CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

fix(ai): the CI healer says why it gave up, and stops truncating its own answer #5574

MergedXSccantynz wants to mergefix/ci-healer-diagnosismainopened 2d ago
ccantynzcommented 2d ago

Measured in production today

ai.ci.gave_up            405        ai.ci.healed   0
aiCostEvents ci_healer   175
repair_flywheel rows     0

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" and cat: command not found — each a one-line fix I then made by hand.

Why nobody noticed

All 405 rows say the same sentence:

ai.ci.gave_up { reason: "unfixable or analysis returned null" }

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

analyzeFailedWorkflowRun now reports a discriminated reason for every null, and healOneRun records it — plus the model's stop_reason — in the audit row. The legacy sentence stays for continuity with the 405 existing rows; diagnosis is the field worth reading from here on.

The probable cause, fixed in the same breath

max_tokens was 2048. A root cause plus several fix descriptions doesn't reliably fit, and JSON truncated at the cap is invalid — it fails both fallbacks in parseJsonResponse() 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_reason will 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, and max_tokens can't be tidied back below 8000 without a test arguing.

Deliberately not in this PR

MODEL_SONNET is claude-sonnet-4-6, a generation behind claude-sonnet-5. Worth revisiting — but a model swap is a separate decision from making the failure legible, and doing both at once would confound the stop_reason evidence this change exists to collect.

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.

Analyze →
⮌ Merged

This pull request was merged into main.

c comment · e edit title · m merge · a approve · r request changes · ? shortcuts