Skip to content

pi: agent adapter rejects valid JSON when model prefixes a prose summary (3 consecutive failures → abort) #154

Description

@cjunxiang

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

  1. gnhf "do some non-trivial task" --agent pi
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions