Skip to content

fix(acpx-provider): surface stopReason as typed error codes#90

Open
coletebou wants to merge 1 commit into
openclaw:mainfrom
coletebou:pr/acpx-stop-reason
Open

fix(acpx-provider): surface stopReason as typed error codes#90
coletebou wants to merge 1 commit into
openclaw:mainfrom
coletebou:pr/acpx-stop-reason

Conversation

@coletebou
Copy link
Copy Markdown

Summary

extractAcpxJson (src/provider.ts) currently parses concatenated agent_message_chunk text and never inspects the JSON-RPC result.stopReason envelope. So when an acpx prompt finishes with stopReason: cancelled / refusal / max_tokens (and emits no message chunk text), clawpatch reports code: "malformed-output" — the same code as a real envelope-shape regression. Operators can't distinguish.

This PR detects the terminal envelope and surfaces non-end_turn stopReasons as typed ClawpatchErrors:

  • cancelledcode: "agent-cancelled"
  • refusalcode: "agent-refused"
  • max_tokens / max_turn_requestscode: "agent-truncated"
  • (anything else) → code: "agent-cancelled" (defensive default)

end_turn keeps the existing chunk-parsing success path. extractAcpxJson is byte-compatible for happy-path output.

Why

Run 20260517T190759-3c9e9e had 37 malformed-output errors against acpx, several of which observed only [available_commands_update, usage_update] — i.e. the prompt finished without producing a message chunk at all. Today these look identical to a parse failure; after this PR they get a separate diagnostic so operators can fix the underlying cause (permission denial, refusal, timeout) instead of guessing.

Envelope shape used

Verified live against acpx@0.8.0 + claude-agent-acp@0.31.4:

{"jsonrpc":"2.0","id":2,"result":{"stopReason":"end_turn","usage":{...}}}

acpx parses the same envelope internally via parsePromptStopReason.

Files

  • src/provider.ts — terminal-envelope pre-pass + 3 new error codes
  • src/provider.test.ts — 6 fixture-driven tests via __testing.extractAcpxJson

Validation

  • pnpm format:check — clean
  • pnpm typecheck — clean
  • pnpm lint — clean
  • pnpm build — clean
  • pnpm test — 547 passed, 1 skipped (+6 new)

Notes

  • ACPX_TESTED_VERSIONS left at ^0.8.0; bump in a follow-up after this lands and runs in the wild.
  • Composes cleanly with the schema-tolerance / safeparse-partition PRs.

Read acpx's terminal JSON-RPC result envelope
(`{id, result: {stopReason, ...}}`) inside `extractAcpxJson` and map
non-`end_turn` reasons to typed `ClawpatchError` codes:

  cancelled          -> agent-cancelled (exit 6)
  refusal            -> agent-refused   (exit 7)
  max_tokens         -> agent-truncated (exit 8)
  max_turns_exceeded -> agent-truncated (exit 8)
  unknown reason     -> agent-cancelled (exit 8) defensively

Previously, prompts that ended in `cancelled` / `refusal` / `max_tokens`
emitted no `agent_message_chunk`, so they fell into the same
`code: "malformed-output"` bucket as a real envelope-shape regression
(see 20260517T184420-d52a72: four parser errors of three different
shapes, all opaque `malformed-output`). On-call had to read raw NDJSON
to triage.

Now `malformed-output` keeps its narrow meaning: clawpatch could not
parse a payload acpx claims is `end_turn`. The `end_turn`-with-no-chunks
path also gets a clearer diagnostic noting the parser bug vs envelope
break.

Verified live against acpx 0.8.0 + claude-agent-acp 0.31.4. Terminal
envelope shape:
  {"jsonrpc":"2.0","id":2,"result":{"stopReason":"end_turn","usage":{...}}}

`ACPX_TESTED_VERSIONS` stays at `^0.8.0`; the stopReason envelope is
present in this version.
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.

1 participant