fix(ai): the CI healer says why it gave up, and stops truncating its own answer #5574
ccantynzAI Reviewcommented 2d ago
AI Triage
(no summary)
Priority: medium Risk area: mixed
Suggested labels: (no label suggestions) Suggested reviewers: (no reviewer suggestions)
Suggestions only — nothing has been applied. The PR author stays in control.
gluecron[bot]🤖 botAI Reviewcommented 2d ago
AI review unavailable
The platform's AI balance is exhausted, so AI generation is temporarily unavailable. Nothing was lost. You can queue this as a repair for the internal agent from the repository's Health page, or try again once the balance is restored. The PR is otherwise unchanged.
Cross-repo impact
See what breaks downstream if this PR merges.
⮌ Merged
This pull request was merged into main.
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts
Measured in production today
The self-healing loop isn't missing — it's running and producing nothing. Every autopilot tick, reaching Claude 175 billed times, and the flywheel table it exists to fill has been empty since it was built. It saw all twelve of today's red runs, including ones whose failures were
Executable not found in $PATH: "bash"andcat: command not found— each a one-line fix I then made by hand.Why nobody noticed
All 405 rows say the same sentence:
That cannot distinguish a model that declined from an answer we truncated from a client that never initialised from a call that threw. Five problems, five different fixes, one label — so 405 occurrences taught nobody anything.
This is the same defect already fixed twice in this session: a typecheck that printed only
exit 137, and a CI log that printed failing test names without their assertions. A failure that cannot say what it was is not a signal.The fix
analyzeFailedWorkflowRunnow reports a discriminated reason for every null, andhealOneRunrecords it — plus the model'sstop_reason— in the audit row. The legacy sentence stays for continuity with the 405 existing rows;diagnosisis the field worth reading from here on.The probable cause, fixed in the same breath
max_tokenswas 2048. A root cause plus several fix descriptions doesn't reliably fit, and JSON truncated at the cap is invalid — it fails both fallbacks inparseJsonResponse()and returns the identical null to a genuine refusal. Raised to 16000, per Anthropic's guidance not to lowball it.I'm not claiming this as proven. The recorded
stop_reasonwill settle it within one tick — which is precisely why it's recorded.Tests
Four, credential-free. Every null path must name itself (verified by mutation — deleting one diagnosis call fails it), the eight reasons stay distinct, the truncation shape carries
stop_reason, andmax_tokenscan't be tidied back below 8000 without a test arguing.Deliberately not in this PR
MODEL_SONNETisclaude-sonnet-4-6, a generation behindclaude-sonnet-5. Worth revisiting — but a model swap is a separate decision from making the failure legible, and doing both at once would confound thestop_reasonevidence this change exists to collect.