From 4d28e084dd2b0a718b9dfb21aea9ba218dcb67f9 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Sat, 9 May 2026 11:19:06 +0200 Subject: [PATCH] fix(runner): NODE_PATH voor pg-resolution + cache-bust ARG + soft quota-probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drie kleine fixes voor de runner uit PBI-4 die in een lokale smoke-test naar boven kwamen: 1. NODE_PATH=/opt/scrum4me-mcp/node_modules in Dockerfile ENV — anders vindt tsx de top-level `pg` import in bin/run-one-job.ts niet (resolve start vanaf /opt/agent/bin/, zoekt geen scrum4me-mcp/node_modules). 2. ARG MCP_CACHE_BUST in Dockerfile vóór de scrum4me-mcp clone-laag. BuildKit cached anders de clone op MCP_GIT_REF=main, ook als main intussen nieuwere commits heeft. Rebuild met `--build-arg MCP_CACHE_BUST=$(date +%s)` invalidate't deze laag deterministisch. 3. quotaProbe in run-one-job.ts soft-failt nu bij niet-zero exit, geen pct-veld, of geen rate-limit-headers in response. De Anthropic API retourneert niet altijd headers; dit zou de runner niet hard moeten crashen. Komt overeen met CLAUDE.md stap 0.4 ("anders: ga door"). Lokale smoke-test bevestigt nu dat een IDEA_GRILL job correct geclaimd wordt met `--model=claude-sonnet-4-6 --permission-mode=plan --effort=high` en de juiste 10 allowed_tools. Apart probleem ontdekt (NIET in deze PR): IDEA_GRILL/IDEA_MAKE_PLAN/ PLAN_CHAT draaien default in --permission-mode plan. In autonomous batch- mode kan Claude in plan-mode mogelijk geen update_job_status aanroepen (plan-mode wacht op human approval), waardoor jobs FAILED raken na 2x lease-expiry. Verdient eigen issue/PR voor permission_mode review. Co-Authored-By: Claude Opus 4.7 (1M context) --- Dockerfile | 10 ++++++++-- bin/run-one-job.ts | 20 ++++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0900239..98fe2b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,8 +55,13 @@ RUN curl -fsSL https://claude.ai/install.sh | bash -s ${CLAUDE_CODE_VERSION} \ # niet om te builden. Pin via build-arg; default = main. ARG MCP_GIT_REPO=https://github.com/madhura68/scrum4me-mcp.git ARG MCP_GIT_REF=main +# Cache-bust voor de clone-laag: hetzelfde MCP_GIT_REF kan tussen rebuilds +# een ander commit aanwijzen (bv. main na een merge). Geef als build-arg +# `--build-arg MCP_CACHE_BUST=$(date +%s)` mee om deze laag te invalidaten. +ARG MCP_CACHE_BUST=1 -RUN git clone --branch ${MCP_GIT_REF} --depth 1 \ +RUN echo "cache-bust=${MCP_CACHE_BUST}" \ + && git clone --branch ${MCP_GIT_REF} --depth 1 \ ${MCP_GIT_REPO} /opt/scrum4me-mcp \ && cd /opt/scrum4me-mcp \ && npm ci --omit=dev --omit=optional || npm install --omit=dev \ @@ -112,7 +117,8 @@ ENV PATH=/opt/agent/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin: AGENT_REPO_CACHE=/var/cache/repos \ AGENT_JOB_ROOT=/tmp \ AGENT_HEALTH_PORT=8080 \ - SCRUM4ME_MCP_DIR=/opt/scrum4me-mcp + SCRUM4ME_MCP_DIR=/opt/scrum4me-mcp \ + NODE_PATH=/opt/scrum4me-mcp/node_modules EXPOSE 8080 diff --git a/bin/run-one-job.ts b/bin/run-one-job.ts index 2aaa50c..cc1ce0b 100644 --- a/bin/run-one-job.ts +++ b/bin/run-one-job.ts @@ -60,24 +60,28 @@ const TOKEN_EXPIRY_PATTERNS: RegExp[] = [ ] // ----- quota probe ---------------------------------------------------- +// Soft-fail: als de probe geen rate-limit-headers krijgt (sommige Anthropic +// endpoints retourneren ze niet) of een transient netwerkfout heeft, log +// een warning en ga door. Alleen bij gemeten quota-overschrijding sleepen. +// Dit spiegelt het gedrag van CLAUDE.md stap 0.4 ("anders: ga door"). async function quotaProbe(userId: string): Promise { const probe = spawnSync(QUOTA_PROBE_PATH, [], { encoding: 'utf8' }) if (probe.status !== 0) { - logError( - `quota probe failed: status=${probe.status} stderr=${(probe.stderr ?? '').trim()}`, + log( + `quota probe non-zero status=${probe.status} stdout=${probe.stdout.slice(0, 200).trim()} — continuing without gate`, ) - throw new Error('quota probe failed') + return } - let parsed: { pct?: number; limit?: number; remaining?: number; reset_at_iso?: string } + let parsed: { pct?: number; limit?: number; remaining?: number; reset_at_iso?: string; error?: string } try { parsed = JSON.parse(probe.stdout) } catch { - logError(`quota probe stdout not JSON: ${probe.stdout.slice(0, 200)}`) - throw new Error('quota probe stdout invalid') + log(`quota probe stdout not JSON (continuing): ${probe.stdout.slice(0, 200)}`) + return } if (parsed.pct === undefined) { - logError(`quota probe missing pct field: ${probe.stdout.slice(0, 200)}`) - throw new Error('quota probe missing pct') + log(`quota probe no pct (continuing): error=${parsed.error ?? '-'}`) + return } const user = await prisma.user.findUnique({