Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
Janpeter Visser
28ef6818a3
Merge pull request #20 from madhura68/fix/token-expired-false-positive
fix(worker): TOKEN_EXPIRED-detectie alleen bij non-zero Claude-exit
2026-05-15 01:03:01 +02:00
Janpeter Visser
a051bb00d4 fix(worker): TOKEN_EXPIRED-detectie alleen bij non-zero Claude-exit
run-one-job.ts scant de volledige stream-json output (incl. álle
tool-results) op auth-error-patronen, en run-agent.sh grept hetzelfde
over het complete run-log — beide zonder de exit-code te checken.

Daardoor legt een geslaagde job (exit 0, result.is_error=false) de
worker plat zodra z'n output toevallig iets als "401 unauthorized"
bevat — bv. wanneer de agent een doc over route-handler-auth leest of
een endpoint test. run-agent.sh doet dan touch TOKEN_EXPIRED + sleep
infinity en de worker draait pas na een rebuild weer.

Fix: detectie gaten op een niet-nul exit. Een echte credential-fout
laat 'claude' non-zero exiten, dus echte expiries worden nog steeds
gevangen — alleen de false positives op geslaagde runs verdwijnen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:43:32 +02:00
Janpeter Visser
1a87bee280
Merge pull request #19 from madhura68/feat/configurable-claude-output-format
feat(worker): configureerbare Claude --output-format, default stream-json (IDEA-064)
2026-05-14 19:32:10 +02:00
Janpeter Visser
c64c0278f2 feat(worker): configureerbare Claude --output-format, default stream-json (IDEA-064)
run-one-job.ts spawnde Claude met een hardcoded --output-format text,
dus de run-log bevatte alleen Claude's eind-samenvatting — geen zicht op
het werk tijdens een job (~6-10 min stilte, dan ineens de samenvatting).

- --output-format komt nu uit AGENT_CLAUDE_OUTPUT_FORMAT (default
  'stream-json'). stream-json streamt elke tool-call / elk bericht live
  naar de run-log; --verbose wordt automatisch toegevoegd want
  print-mode vereist dat bij stream-json.
- Zet AGENT_CLAUDE_OUTPUT_FORMAT=text terug voor de oude terse output.
- .env.example: nieuwe var gedocumenteerd.

stdoutBuf wordt alleen voor de TOKEN_EXPIRED-regexscan gebruikt; de
auth-error-strings staan ook binnen de JSON-events, dus detectie werkt
ongewijzigd. Niets parseert de output als job-resultaat.

Gevolg: de run-log (en de jobs/<job_id>.log symlink uit IDEA-063) wordt
JSONL i.p.v. plain text — gebruik jq of een viewer. Log-grootte groeit;
rotate-logs.sh dekt dat al af.

node --check + type-strip schoon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:29:14 +02:00
Janpeter Visser
794ad7faaa
Merge pull request #18 from madhura68/feat/per-job-log-symlink
feat(logs): per-job log-symlink jobs/<job_id>.log (IDEA-063)
2026-05-14 19:26:13 +02:00
3 changed files with 35 additions and 9 deletions

View file

@ -108,3 +108,9 @@ AGENT_BACKOFF_MAX=300
AGENT_LOG_GZIP_AFTER_HOURS=24
# Hoeveel dagen ge-gzipte logs bewaren voor we ze verwijderen.
AGENT_LOG_DELETE_AFTER_DAYS=30
# Claude CLI --output-format. Default 'stream-json' streamt de volledige
# event-stream (tool-calls, berichten) live naar de run-log; 'text' geeft
# alleen Claude's eind-samenvatting (terser, maar geen live-meekijken).
# stream-json maakt de run-log JSONL — gebruik jq of een viewer.
AGENT_CLAUDE_OUTPUT_FORMAT=stream-json

View file

@ -80,8 +80,12 @@ while true; do
# Token-expiry detectie: run-one-job.ts retourneert exit 3 wanneer het
# bekende auth-error-strings in Claude's output ziet. We checken óók de
# log-tekst voor het geval een ander pad het patroon raakt (bv. Prisma-
# connection-error met OAuth-expired in error-body).
if [[ "$exit_code" -eq 3 ]] || grep -qE '(invalid_api_key|authentication.*failed|401.*unauthor|OAuth.*expired)' "${run_log}"; then
# connection-error met OAuth-expired in error-body) — maar alléén bij een
# niet-nul exit. Het run-log bevat de volledige stream-json output (incl.
# tool-results én run-one-job's eigen "TOKEN_EXPIRED detected"-logregel),
# dus een geslaagde job die toevallig "401 unauthorized" in z'n output
# heeft mag de grep-fallback niet triggeren.
if [[ "$exit_code" -eq 3 ]] || { [[ "$exit_code" -ne 0 ]] && grep -qE '(invalid_api_key|authentication.*failed|401.*unauthor|OAuth.*expired)' "${run_log}"; }; then
log "AUTH FAILURE detected (exit=$exit_code or pattern in log) — marking TOKEN_EXPIRED"
touch "${AGENT_STATE_DIR}/TOKEN_EXPIRED"
write_state "$(jq -n \

View file

@ -292,6 +292,13 @@ async function main(): Promise<number> {
// 7. Build CLI args.
const promptText = getKindPromptText(ctx.kind).replace('$PAYLOAD_PATH', payloadPath)
// --output-format is configureerbaar via env. Default 'stream-json' geeft
// de volledige event-stream (elke tool-call, elk bericht) live in de
// run-log, i.p.v. alleen Claude's eind-samenvatting. stream-json vereist
// --verbose in print-mode. Zet AGENT_CLAUDE_OUTPUT_FORMAT=text terug voor
// de oude terse output. TOKEN_EXPIRED-detectie werkt ongewijzigd: de
// auth-error-strings staan ook binnen de JSON-events.
const outputFormat = process.env.AGENT_CLAUDE_OUTPUT_FORMAT ?? 'stream-json'
const args: string[] = [
'-p',
promptText,
@ -306,8 +313,9 @@ async function main(): Promise<number> {
'--add-dir',
'/opt/agent',
'--output-format',
'text',
outputFormat,
]
if (outputFormat === 'stream-json') args.push('--verbose')
if (effort) args.push('--effort', effort)
const cwd = worktreePath ?? '/opt/agent'
@ -375,13 +383,21 @@ async function main(): Promise<number> {
`duration_ms=${durationMs} wall_clock_seconds=${Math.round(durationMs / 1000)}`,
)
// 10. Token-expiry detection.
// 10. Token-expiry detection — alleen als Claude zelf non-zero eindigde.
// stdoutBuf bevat de volledige stream-json output incl. álle tool-results,
// dus de auth-error-strings kunnen ook agent-werk-content zijn (een doc
// over 401-handling gelezen, een endpoint getest). Een echte credential-
// fout laat 'claude' non-zero exiten; een geslaagde run (exit 0) is per
// definitie geen token-expiry. Zonder deze gate legt zulke content de
// worker onterecht plat (run-agent.sh → TOKEN_EXPIRED marker + sleep).
let tokenExpired = false
for (const pat of TOKEN_EXPIRY_PATTERNS) {
if (pat.test(stdoutBuf)) {
tokenExpired = true
log(`TOKEN_EXPIRED detected pattern="${pat.source}" exiting code=3`)
break
if (exitCode !== 0) {
for (const pat of TOKEN_EXPIRY_PATTERNS) {
if (pat.test(stdoutBuf)) {
tokenExpired = true
log(`TOKEN_EXPIRED detected pattern="${pat.source}" exiting code=3`)
break
}
}
}