diff --git a/bin/log-cleanup.sh b/bin/log-cleanup.sh index dbd8a0d..5a4ad85 100755 --- a/bin/log-cleanup.sh +++ b/bin/log-cleanup.sh @@ -18,4 +18,9 @@ find "${AGENT_LOG_DIR}" -type f \ \( -name '*.log' -o -name '*.log.gz' -o -name '*.txt' -o -name '*.json' \) \ -mtime "+${AGENT_LOG_HARD_DELETE_DAYS}" -delete 2>/dev/null || true +# Prune dangling per-job symlinks: jobs/.log -> runs/.log waarvan +# het doel door rotatie is gegzipt of verwijderd. De -type f hierboven raakt +# symlinks niet, dus broken links worden hier expliciet opgeruimd (-xtype l). +find "${AGENT_LOG_DIR}/jobs" -maxdepth 1 -xtype l -delete 2>/dev/null || true + find "${AGENT_LOG_DIR}/jobs" -mindepth 1 -type d -empty -delete 2>/dev/null || true diff --git a/bin/run-agent.sh b/bin/run-agent.sh index c67213a..52c6f49 100644 --- a/bin/run-agent.sh +++ b/bin/run-agent.sh @@ -68,7 +68,9 @@ while true; do # claimt zelf via tryClaimJob, leest JobConfig (PBI-67), bouwt de # juiste Claude CLI-args, spawnt 'claude', wacht, sluit af. set +e - tsx /opt/agent/bin/run-one-job.ts > "${run_log}" 2>&1 + # RUN_LOG laat run-one-job.ts een jobs/.log symlink leggen naar + # dit run-log, zodat de output van een job op job-id vindbaar is. + RUN_LOG="${run_log}" tsx /opt/agent/bin/run-one-job.ts > "${run_log}" 2>&1 exit_code=$? set -e diff --git a/bin/run-one-job.ts b/bin/run-one-job.ts index 3da7cf9..cd0919c 100644 --- a/bin/run-one-job.ts +++ b/bin/run-one-job.ts @@ -22,7 +22,8 @@ // 3 = TOKEN_EXPIRED detected → run-agent.sh schrijft TOKEN_EXPIRED marker import { spawn, spawnSync } from 'node:child_process' -import { mkdirSync, rmSync, writeFileSync } from 'node:fs' +import { mkdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' +import { basename, join } from 'node:path' import { Client as PgClient } from 'pg' @@ -196,6 +197,25 @@ async function main(): Promise { log(`claimed job_id=${jobId}`) + // Per-job log: symlink jobs/.log -> the runs/.log of + // this iteration. runs/ files are timestamp-named, so without this a job's + // output is only findable by grepping. run-agent.sh passes the run-log + // path via RUN_LOG. Relative target so it survives the host bind-mount. + // Best-effort — never fail the job over a log convenience. Dangling links + // (after the runs/ file is gzipped/deleted) are pruned by log-cleanup.sh. + const runLog = process.env.RUN_LOG + if (runLog) { + try { + const jobsDir = join(process.env.AGENT_LOG_DIR ?? '/var/log/agent', 'jobs') + mkdirSync(jobsDir, { recursive: true }) + const linkPath = join(jobsDir, `${jobId}.log`) + rmSync(linkPath, { force: true }) + symlinkSync(join('..', 'runs', basename(runLog)), linkPath) + } catch (err) { + log(`per-job log symlink skipped for ${jobId}: ${(err as Error).message}`) + } + } + // 3. Resolve full context. let ctx: Awaited> = null try {