Skip to content

fix(agent): build/verify failure diagnosis — timeout classify, claude-binary Exec-format, failing-line surfacing#597

Open
isadeks wants to merge 4 commits into
pr/ecs-substrate-hardeningfrom
pr/agent-build-robustness
Open

fix(agent): build/verify failure diagnosis — timeout classify, claude-binary Exec-format, failing-line surfacing#597
isadeks wants to merge 4 commits into
pr/ecs-substrate-hardeningfrom
pr/agent-build-robustness

Conversation

@isadeks

@isadeks isadeks commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Stacked on #596 (pr/ecs-substrate-hardening) → #503. Merge those first; this PR then retargets. Diff shown is against #596's branch.

Summary

Platform-agnostic robustness fixes for the agent's build/verify path, surfaced dogfooding ABCA-on-ABCA on the ECS build box but not ECS-specific — they improve failure diagnosis for any repo/substrate.

What changed

  • fix(errors): classify a build/verify command TIMEOUT — a TimeoutExpired from a long build previously fell through to a bare "Unexpected error"; now classified as a user-actionable timeout (retry / raise BUILD_VERIFY_TIMEOUT_S), retryable: true. Live-caught (ABCA-667).
  • fix(agent): place the claude-code native binary at image buildclaude-code@2.1.x ships an error-shim at bin/claude.exe that its postinstall replaces with the platform-native ELF; the postinstall silently fell back at build time, leaving the shim on PATH → every agent died with Exec format error. Now re-run install.cjs + hard-verify claude --version so a broken shim fails the image build, not every task.
  • fix(agent): log stdout on run_cmd failure — build-gate failures were undebuggable without the command output.
  • fix(agent): surface the failing line from a parallel build DAG — a fan-out mise run build failure showed only a tail; now surfaces the line that actually names the failure.

Testing

  • Full mise run build green (CDK + agent + CLI); new/updated tests in error-classifier.test.ts, test_shell.py.
  • Live-verified: the arm64-native-claude fix confirmed in the deployed image (claude --version → real 2.1.191, not the error shim).

Notes for reviewers

  • No compute-backend coupling — pure agent-runtime + error-classifier changes. Stacked here only because they were validated together with the ECS substrate work.

isadeks added 4 commits July 13, 2026 19:52
…A-667, was 'Unexpected error')

Replication finding: a live fork coding child (ABCA-667) failed with
'TimeoutExpired: Command [mise run build] timed out after 600 seconds' — the full
~2800-test fork build exceeded the 600s cap. That's the pre-agent verify-build
subprocess timeout, which lands raw in error_message and fell through to
'Unexpected error' + generic guidance — exactly the transient-vs-real confusion
this rework targets. It's not a crash and not broken code: the build ran too long.

Add a TIMEOUT classification for  /
 → 'Build/tests didn't finish in time', user class,
retryable, remedy names the real fix (retry / raise BUILD_VERIFY_TIMEOUT_S or move
to the larger ECS build box). Ordered before the generic poll-timeout pattern.
Test uses the exact ABCA-667 shape. 60 classifier tests green.

(cherry picked from commit 6394a59)
(cherry picked from commit b9c1a94)
… (Exec format error)

claude-code@2.1.191 uses a shim (bin/claude.exe) + platform-optional-dependency
install model: its postinstall (install.cjs) is supposed to replace the shim
with the platform-native ELF binary. When that postinstall silently falls back
(e.g. it hits EACCES overwriting the shim under npm's root de-privileging), the
error-shim stays on PATH and every agent run dies at the run_agent step with
'OSError: [Errno 8] Exec format error: \'claude\'' — even though the native
arm64 binary is present in the image as an optional dep, just never wired up.
Live-caught on ABCA-659's retry: all 3 ECS runs failed this way on a freshly
rebuilt image (the prior working image ran the older single-binary 2.1.142).

Re-run install.cjs explicitly after the npm install so the native binary is
placed at build time, and hard-verify with 'claude --version' so a still-broken
shim fails the image build instead of every task at runtime.

(cherry picked from commit 7cb4a87)
(cherry picked from commit 7de778b)
…debuggable

The post-agent build gate logged only stderr on a non-zero exit. But build/test
tooling (jest, tsc, the mise task DAG) writes the ACTUAL failing-task error to
STDOUT, not stderr — stderr often carries only the runner's plan echo. So a red
`mise run build` surfaced every task STARTING in CloudWatch but never WHICH one
failed or why (ABCA-662: a 7s exit-1 with zero captured cause, indistinguishable
from a phantom, and the reason Linear showed a build-fail GitHub CI passed).

run_cmd now also tails the last 20 lines of stdout on failure (redacted — repo
build output is untrusted). 4 tests pin: stdout surfaced, tailed-not-headed,
redacted, and NOT dumped on success.

(cherry picked from commit 67d84db)
(cherry picked from commit bb578fd)
…ust a tail

The stdout-on-failure logging (67d84db) tailed the last 20 lines — enough for a
serial command, but NOT for `mise run build`, which runs 4 packages in PARALLEL
and interleaves their output. The failing task's error lands in the MIDDLE while
the tail captures whatever finished LAST (e.g. a PASSING package's coverage
table) — so the log showed a green coverage table on a red build and named
nothing (ABCA-662: three verification runs where the actual failing sub-task was
never visible in CloudWatch).

run_cmd now scans the WHOLE stdout for failure-signature lines (FAIL/✕/●/error
TS/ELIFECYCLE/coverage-threshold/mise 'no task'/'task failed'/traceback/…),
filters benign noise ('0 errors', '--no-error'), and logs those FIRST, then a
trailing tail for context — deduped, capped at 40 lines. Falls back to the tail
when nothing matches (unknown tool). This is the tooling gap that made the whole
ABCA-662 build investigation slow: every failure was diagnosable only by luck or
local repro. 5 tests incl. the mid-DAG-failure + coverage-threshold cases.

(cherry picked from commit 3fe99c9)
(cherry picked from commit aaa4f18)
@isadeks isadeks requested review from a team as code owners July 13, 2026 23:57
@isadeks isadeks added bug Something isn't working agent-runtime Python agent container: pipeline, runner, hooks, prompts, tools, Dockerfile validation-loop Tasks related to improve the validation loop for ABCA's codebase P2 lowest priority labels Jul 14, 2026

@scottschreckengaust scottschreckengaust left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — Principal Architect (Stack A: ECS substrate, PR 3/4)

Base verified against fresh origin PR head (diffed vs #596). Mandatory agents run stack-wide (full table on the #596 review).

Verdict: Approve (with a nit)

Solid build/verify-diagnosis work, all three changes tied to live incidents:

  • _surface_failure_lines (agent/src/shell.py) — scans the WHOLE stdout for failure-signature lines, not just the tail. Correct for a parallel mise DAG that interleaves output so the failing task's line lands in the middle while the tail is a passing package's coverage table (ABCA-662). Capped at 40 + a 15-line tail, deduped, and redacted (redact_secrets) since repo build output is untrusted. The return out or tail fallback guarantees a failure never logs nothing. Verified run_cmd's check=True path still raises — the added stdout logging is purely additive, not a swallow.
  • Error-classifier TIMEOUT pattern (cdk/src/handlers/shared/error-classifier.ts) — classifies a build/verify wall-clock kill distinctly from a crash (retryable: true, user-actionable remedy). Verified it's ordered before the generic catch-all and isn't shadowed by the earlier poll timeout exceeded rule.
  • Dockerfile claude install.cjs re-run + claude --version — a real fix: claude-code@2.1.x ships an error-shim that its postinstall can silently leave in place, dying at runtime with "Exec format error". Re-running the installer and hard-verifying it exec's fails the build instead of the task.

Tests (test_shell.py::TestRunCmdFailureLogging, error-classifier.test.ts) cover the key cases well: stdout-logged-on-failure, mid-DAG line surfaced, coverage-threshold surfaced, benign "0 errors" not falsely elevated, redaction, success-doesn't-dump, no-marker→tail fallback, and the timeout classified as retryable-not-Unexpected.

Non-blocking nit

  • N5 — Broad failure markers. _FAILURE_LINE_MARKERS includes broad substrings ("error:", "failed") that could surface benign advisory lines. It's logging-only, capped, redacted, and the _FAILURE_LINE_NOISE allowlist covers common false positives — so worst case is slightly noisier CloudWatch, no functional impact. Flagging for awareness only.

Governance note

Branch pr/agent-build-robustness carries no issue number and the body references only stacked PRs (ADR-003). Same governance item raised on #596 — please attach an approved issue and a conforming branch name before merge. Code is approve-ready.

CI

All green (title, CodeQL ×3, dead-code advisory, secrets/deps/actions, build (agentcore)); MERGEABLE. No CDK construct/stack change → bootstrap synth-coverage N/A.

🤖 Generated with Claude Code

@isadeks

isadeks commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the approve. On N5 (broad _FAILURE_LINE_MARKERS substrings like "error:"/"failed"): leaving as-is by intent — it's logging-only, capped at 40 + 15-line tail, redacted, and the _FAILURE_LINE_NOISE allowlist covers the common false positives, so worst case is slightly noisier CloudWatch with no functional impact (matches your "awareness only" read). If a specific benign line shows up noisy in practice, the fix is a one-line allowlist add rather than narrowing the markers (which would risk missing a real failure line). Governance (approved issue + branch rename) tracked with the rest of Stack A.

@isadeks isadeks left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-reference: Dockerfile fix overlaps #613 (and complements #599)

Flagging a coordination point surfaced while reviewing #613 — the claude-binary Exec format error is being addressed across three open PRs:

  • This PR (#597) and #613 both append node install.cjs to the same RUN … npm --prefix … update … line in agent/Dockerfile, so they will textually conflict — both can't merge as-is. #597's version is the superset: it also adds && claude --version, hard-gating the image build so a silent postinstall fallback fails docker build rather than every task at runtime (strictly better than restore-and-hope), plus a comment explaining the shim mechanism.
  • #599 is complementary, not conflicting — it classifies the same Exec format error into a user-facing message (error-classifier.ts, disjoint file). Prevention (here/#613) + legibility (#599) are the two halves of ABCA-659.

Suggested resolution: since #597's Dockerfile hunk supersedes #613's, either close #613 in favor of this PR, or — if this PR stays blocked behind its stack (#596#503; #503 merged, #596 still open) and the exec-format break is hurting people now — let #613 merge against main and drop the Dockerfile hunk here on rebase. Whoever lands first wins the line; the other should rebase around it.

(I verified install.cjs is idempotent and exists in @anthropic-ai/claude-code@2.1.191; couldn't reproduce the npm update clobber on linux/amd64, so it looks arm64/AgentCore-specific — consistent with the live-caught reports. The && claude --version gate here is the right belt-and-braces regardless.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-runtime Python agent container: pipeline, runner, hooks, prompts, tools, Dockerfile bug Something isn't working P2 lowest priority validation-loop Tasks related to improve the validation loop for ABCA's codebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants