Summary
gnhf with --agent pi fails every iteration with Failed to parse pi output: ... is not valid JSON, hits the maxConsecutiveFailures limit, and aborts — even though the model did emit valid JSON matching the schema. After the abort the TUI stays open with the timer still incrementing, so the run looks stuck/active when it has actually stopped.
│ × gnhf stopped │
│ pi ran for 1h 16m before: Failed to parse pi output: Unexpected token 'I', "Iteration "... is not valid JSON │
Root cause
PiAgent is the only built-in agent adapter that parses its final assistant message with raw JSON.parse. Every other adapter (acp, copilot, opencode, rovodev) routes through parseAgentJson (src/core/agents/json-extract.ts), which strips Markdown fences and recovers the rightmost balanced JSON object — i.e. it tolerates prose/fences around the JSON.
Pi (the agent) frequently writes a short prose summary before the JSON object. The final assistant message observed in a real run was:
Iteration 3 complete. Established a clean, building petpet foundation ...
{"success":true,"summary":"...","key_changes_made":[...],"key_learnings":[...]}
The leading Iteration 3 complete. makes JSON.parse(finalText) throw Unexpected token 'I', "Iteration "... is not valid JSON, so the iteration is recorded as a failure. Reproduced across three iterations ("Build gree...", "All work f...", "Iteration ").
parseAgentJson already recovers the JSON object from exactly that text (verified against the captured message_end event: all four required keys present, success: true).
Impact
- Every pi iteration that prefixes prose fails → guaranteed trip of
maxConsecutiveFailures (default 3) → orchestrator abort.
- Downstream UX: after abort,
cli.ts keeps the TUI open (keepTui = status === "aborted" && isTTY) while the renderer's setInterval keeps ticking, so the elapsed timer keeps going up and the run looks stuck until the user hits Ctrl+C. (Separate concern, but it is what surfaces this bug most visibly.)
Repro
gnhf "do some non-trivial task" --agent pi
- Observe each iteration fail with
Failed to parse pi output: ... is not valid JSON where the quoted fragment is prose (e.g. Iteration ), not the JSON object that actually follows.
Suggested fix
Bring PiAgent to parity with the other adapters: parse via parseAgentJson(text, schemaValidator) with a no-predicate fallback (so schema-invalid JSON still surfaces as Invalid pi output rather than Failed to parse pi output). This mirrors the existing opencode adapter. PR incoming via no-mistakes.
Environment
- gnhf 0.1.42 (also reproduced on
main @ fe202c4)
- pi 0.79.9
- darwin, node v26.0.0
Summary
gnhfwith--agent pifails every iteration withFailed to parse pi output: ... is not valid JSON, hits themaxConsecutiveFailureslimit, and aborts — even though the model did emit valid JSON matching the schema. After the abort the TUI stays open with the timer still incrementing, so the run looks stuck/active when it has actually stopped.Root cause
PiAgentis the only built-in agent adapter that parses its final assistant message with rawJSON.parse. Every other adapter (acp,copilot,opencode,rovodev) routes throughparseAgentJson(src/core/agents/json-extract.ts), which strips Markdown fences and recovers the rightmost balanced JSON object — i.e. it tolerates prose/fences around the JSON.Pi (the agent) frequently writes a short prose summary before the JSON object. The final assistant message observed in a real run was:
The leading
Iteration 3 complete.makesJSON.parse(finalText)throwUnexpected token 'I', "Iteration "... is not valid JSON, so the iteration is recorded as a failure. Reproduced across three iterations ("Build gree...","All work f...","Iteration ").parseAgentJsonalready recovers the JSON object from exactly that text (verified against the capturedmessage_endevent: all four required keys present,success: true).Impact
maxConsecutiveFailures(default 3) → orchestrator abort.cli.tskeeps the TUI open (keepTui = status === "aborted" && isTTY) while the renderer'ssetIntervalkeeps ticking, so the elapsed timer keeps going up and the run looks stuck until the user hits Ctrl+C. (Separate concern, but it is what surfaces this bug most visibly.)Repro
gnhf "do some non-trivial task" --agent piFailed to parse pi output: ... is not valid JSONwhere the quoted fragment is prose (e.g.Iteration), not the JSON object that actually follows.Suggested fix
Bring
PiAgentto parity with the other adapters: parse viaparseAgentJson(text, schemaValidator)with a no-predicate fallback (so schema-invalid JSON still surfaces asInvalid pi outputrather thanFailed to parse pi output). This mirrors the existingopencodeadapter. PR incoming viano-mistakes.Environment
main@ fe202c4)