Skip to content

fix(profiles): coder output adapter reassembles result JSON from opencode event fragments#179

Merged
drewstone merged 1 commit into
mainfrom
fix/coder-output-adapter-opencode-events
Jun 6, 2026
Merged

fix(profiles): coder output adapter reassembles result JSON from opencode event fragments#179
drewstone merged 1 commit into
mainfrom
fix/coder-output-adapter-opencode-events

Conversation

@drewstone
Copy link
Copy Markdown
Contributor

Closes #178.

Problem

runLoop + coderProfile returned an empty CoderOutput (patch "", costUsd: 0) for every non-claude-code harness — the validator then graded the emptiness as "no candidate passed validation". Reproduced 9/9 against opencode in live sandbox runs.

Isolated by elimination (all on the same box): bare box.prompt() ✓, bare box.streamPrompt() ✓ (27 events, terminal present), inline AgentProfile ✓, model overrides ✓ — the empty result appeared only through the coder loop.

Root cause

parseCoderEvents scanned each event's data.text / data.delta for the fenced result block. That's claude-code's shape. opencode streams assistant text as incremental message.part.updated fragments (data.part.text, with incremental data.delta), so:

  1. the text lived at data.part.text — never read; and
  2. the result JSON was split across many fragments — present in no single event.

So the agent's real output was invisible and the loop reported an empty patch + $0.

Fix

parseCoderEvents now accumulates assistant text across the whole stream via collectAssistantText, tolerating both shapes (claude-code data.text/data.delta and opencode message.part.updated text parts; reasoning/thinking parts excluded so they can't inject a decoy block), then scans the concatenation for the last valid fenced JSON block. The structured-result-event fast path is unchanged.

Tests

tests/profiles/coder.test.ts (15 pass): added

  • reassembly of a result block split across 7-char message.part.updated deltas, and
  • last-valid-block-wins with a reasoning-part decoy.

pnpm typecheck, pnpm lint, and the profiles/sandbox-events/coder-delegate suites are green.

Note for reviewers

This was the terminal blocker in a long live-debugging session (cli-bridge→Rust migration via the Pi loops extension). Sibling platform issues filed separately: agent-dev-container #1770 (host CD), #1775 (router CF/auth), #1781 (claude-code BYOK in sandbox).

…code event fragments

parseCoderEvents only scanned per-event data.text/data.delta (claude-code's
shape) for the fenced result block. opencode streams assistant text as
incremental message.part.updated fragments (data.part.text / data.delta), so
the final JSON result is split across many events and present in none — the
adapter returned an empty CoderOutput for a run that actually produced a
patch, which runLoop then graded as 'no candidate passed validation' with
$0 cost. Accumulate assistant text across the whole stream (both shapes,
reasoning parts excluded), then scan the concatenation for the last valid
fenced block. Closes #178.
@drewstone drewstone merged commit 6e589e5 into main Jun 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

coderProfile/runLoop returns empty $0 CoderOutput against opencode (and non-claude harnesses) — event/output adapter mismatch

1 participant