Skip to content

fix(acp): show agent details when a chat message fails - #26666

Merged
brennanb2025 merged 1 commit into
mainfrom
brennanb2025/acp-turn-error-details
Oct 9, 2026
Merged

brennanb2025 merged 1 commit into
mainfrom
brennanb2025/acp-turn-error-details

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​109 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​109
Prod 4 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​16 $\color{#cf222e}{\Huge{\mathbf{−}}}$​7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​9

ELI5

When a chat agent such as OpenCode, Grok or OMP fails a message, it often answers with a generic "Internal error" and puts its real reason in a separate details field. Orca only read that field for agents that had their own custom reader, so any other agent's failure could show up as a bare "Internal error". Now Orca reads the agent's reason the same way for every agent.

What Changed

Problem. These agents talk to Orca over the Agent Client Protocol (ACP). When an agent fails a message, its answer carries a short message (often just "Internal error") and an optional data field. Many agents put their actual reason in that data field: as plain text, or as a details text field. Until now Orca only looked there for agents that had a custom reader written for them (OMP read details; Grok read plain text or message). Every other agent, including future ones, showed only the short message.

Change. One shared rule now decides the words in the failure row, for every ACP agent:

  1. the agent's own reader, if it has one (Grok keeps its data.message reader);
  2. otherwise the data, when it is non-empty text;
  3. otherwise data.details, when it is non-empty text;
  4. otherwise the agent's short message.

Structured data that has no words for a person (for example OpenCode's {"service":"session","errorName":"APIError"}) is ignored, so the row never shows raw JSON. OMP's custom reader and Grok's plain-text branch are removed because the shared rule now covers them. "Not signed in" detection and its "Sign in to ." row are unchanged.

What you see. For the agent failures recorded so far, the row reads the same as before: OMP's no-model failure still shows "Sign in to OMP." followed by OMP's own steps, and OpenCode still shows its own message. The difference is for any agent (or a future version of one) that puts its reason in the data field without a custom reader: you now see its words instead of "Internal error".

Why

The bug was per-agent: each agent needed its own reader, and an agent without one lost its reason. One rule for every agent removes that class of bug instead of adding a fourth reader. It lives where the failure row's text is already built, so failed turns, messages refused before a turn starts, and sign-in failures all get the same words.

Intended difference from the common pattern: the common pattern appends the error data as raw JSON when it has no text field. Orca leaves structured data out instead. A real OpenCode failure's data is only metadata (service, errorName), and raw JSON in a row the user reads is noise; the agent's own message is kept.

Intended: text in the data field takes priority over the short message. An agent that put a specific message in message and only a code in details would show the code. None of OMP, Grok or OpenCode is known to answer that way.

Earlier drafts of this PR also prefixed the agent name and kept "Internal error:" in front of the details. Both were dropped when this was rebuilt on main after #26544 and #26649 added sign-in handling, so the sign-in row doesn't name the agent twice.

Linked Issue

Follow-up to #26544 and #26649 (sign-in guidance, which already show OMP's no-model steps). Independent of #26587, which fixes starting an OMP chat when OMP reports no model.

Visual Proof

Windows high spec, real OpenCode 2.0.14 (signed out, isolated home), hidden Orca built from this head. The model "opencode/Claude Haiku 4.5" was picked in the chat's model picker, then one "Hello" was sent. OpenCode answered with message Internal error: Model unavailable: opencode/claude-haiku-4-5 and data {"service":"session","errorName":"provider.no-route"}. The row shows OpenCode's message and no JSON:

OpenCode failure row on Windows

There is no before/after pair: for this failure the row text is the same as before. What changes is that this structured data is deliberately left out, and that agents with words in their data field now show them; no installed agent we have answers that way, so that part is shown by tests only.

OMP's live row is not shown here. On current main an OMP with no model can't start a chat at all ("Chat could not be started."), which #26587 fixes. Once #26587 lands, OMP's failure row comes through the shared rule. Until then, main's sign-in test, which is unchanged here, covers it.

Testing

  • I manually tested these changes locally (Windows, OpenCode, above)

  • Automated tests added/updated

  • New acp-structured-prompt-failure.test.ts (12 tests), through the real protocol reader, translator and SQLite chat journal. It covers OpenCode and Grok with the reason in data.details, as plain-text data, and with no data, plus the recorded real OpenCode error shape (no JSON in the row). It also covers a sign-in failure with details ("Sign in to …" plus the agent's words), and a message refused before its turn starts.

  • 103 of 103 tests pass across the 12 related ACP test files, including main's acp-structured-sign-in.test.ts, unchanged (OMP's "No model selected" row still contains OMP's full steps).

  • orca-ci-checks 23/23, including typecheck. CI on this head: 33 passed, 20 skipped (skipped is not counted as passed), none failed.

  • Live check above: Windows only. Not checked live: macOS, Linux, SSH, mobile, Grok, and OMP (blocked by fix(acp): start chats for agents that report no model #26587, see above).

Review

A fresh adversarial review, with an independent design sketch, found no proven issues. Deferred minor points: Grok's reader returns a blank message without falling through to details; that is the same as before. Some edge cases have no tests yet: whitespace-only data, and the 512-character limit on this path.

Agent skill upstream boundary

  • Not applicable: no upstream skill-installer material is copied.

Notes

The change runs on the machine that runs the agent and only changes the words in an existing row. There is no new wire field, stored field, migration or setting, so older paired clients and SSH hosts just show the new words. Folder workspaces and git worktrees behave the same. One small change: if an agent sends blank text as its data or details, the row now shows its short message, where before OMP's blank details fell through to "OMP ended this turn with an error."

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Screenshot attached (no visual difference for the recorded failure; explained above)
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered
  • CI: no failures; skipped checks are not counted as passes

@brennanb2025

brennanb2025 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Review summary

The problem. When a chat agent (OpenCode, Grok, OMP or a future agent) fails a message, it often sends a generic "Internal error" and keeps its real reason in the error's data field. Orca read that field only for agents with a custom reader, so other agents' failures could show up as a bare "Internal error".

What changes for you. Every agent's failure row now uses one rule: the agent's own reader if it has one, otherwise the text in the data field, otherwise its details text, otherwise the short message. Structured data with no words for a person is left out, so the row never shows raw JSON. For the failures we have on record, the row reads the same as before: OMP's no-model failure still shows "Sign in to OMP." plus OMP's steps, and OpenCode still shows its own message. The gain is for agents that put their reason in the data field without a custom reader.

How this PR got here. The first version predated main's #26544 and #26649, which added sign-in handling and already show OMP's no-model steps. It was rebuilt on main as one shared rule. The rebuild dropped the earlier agent-name prefix and the "Internal error:" prefix so the sign-in row doesn't name the agent twice, and dropped a raw-JSON fallback after a real OpenCode error showed its data is only metadata.

Fixed in review: nothing; the fresh adversarial review found no proven issues.
Deferred (minor): Grok's reader returns a blank message without falling through to details (same as before). There are no tests yet for whitespace-only data or the 512-character limit on this path. When an agent puts text in both places, the text in data wins over the short message; no current agent is known to answer that way.
Intended differences:

  • Structured data is left out instead of appended as raw JSON, the common pattern; raw JSON in a user-facing row is noise.
  • Text in the data field outranks the short message.

Verified:

  • 12 new tests and 103 related tests pass, including main's unchanged sign-in test. orca-ci-checks passes 23/23. CI on this head has no failures (33 passed, 20 skipped; skipped are not counted as passes).
  • Live on Windows high spec: real OpenCode 2.0.14, signed out in an isolated home, with "opencode/Claude Haiku 4.5" picked and one "Hello" sent. The row read exactly "Internal error: Model unavailable: opencode/claude-haiku-4-5" with no JSON. The chat journal held one submission and one error row, not marked as sign-in.
  • The app ran hidden with its own profile and no credentials. All of its processes exited afterwards, and the protected real config files were unchanged.
  • The Windows QA checkout qa-26666-error-detail-rebuilt was created for this run and has been removed.

Not verified live:

  • OMP: on current main, an OMP with no model can't start a chat; fix(acp): start chats for agents that report no model #26587 fixes that, and OMP's live row waits for it.
  • Grok, macOS, Linux, SSH and mobile.
  • Any installed agent that actually sends its words in the data field: none we have does, so that path is covered by tests only.

Agents often answer a failed session/prompt with a generic JSON-RPC message ("Internal error") and keep the real reason in error.data. Only OMP and Grok had hooks that read it, so OpenCode and future ACP agents showed the bare message.

The prompt error detail now reads, for every ACP agent: the agent's hook, else string data, else data.details, else the message. Other structured data is metadata and is ignored. OMP's details hook and Grok's string-data branch are now covered by that rule and removed.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/acp-turn-error-details branch from 3bfd1d5 to 45a92e5 Compare October 9, 2026 02:43
@brennanb2025
brennanb2025 changed the base branch from brennanb2025/omp-windows-chat-start to main October 9, 2026 02:44
@brennanb2025
brennanb2025 marked this pull request as ready for review October 9, 2026 04:37
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Walkthrough

ACP prompt error details now use dialect-specific handling, followed by nonblank string data or details, and then error.message. Grok extracts messages only from structured error data, and OMP no longer defines promptErrorDetail. New tests cover OpenCode and Grok prompt failures, authentication formatting, and Grok rejections before a turn starts. The new test is included in the SQLite runtime test list.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 45a92

Failure rows now show agent-supplied details. A narrow edge case remains where Grok sends a blank message alongside details. It is cosmetic and safely falls back to blank text.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: displaying agent-provided failure details for ACP chat message failures.
Description check Passed The description is complete and focused. It includes the user-facing change, motivation, implementation details, linked issue references, visual proof context, testing results, known limitations, and …
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: df9365b6-b91f-4225-96d9-ee421cb3b017
📥 Commits

Reviewing files that changed from the base of the PR and between 820277c and 45a92e5.

📒 Files selected for processing (5)
  • config/scripts/vitest-sqlite-runtime-files.mjs
  • src/main/acp/acp-dialects/grok-dialect.ts
  • src/main/acp/acp-dialects/omp-dialect.ts
  • src/main/acp/acp-structured-prompt-failure.test.ts
  • src/main/acp/acp-turn-failures.ts
💤 Files with no reviewable changes (1)
  • src/main/acp/acp-dialects/omp-dialect.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.

* message ("Internal error") and keep their own words in `data`. */
export function acpPromptErrorDetail(dialect: AcpDialect, error: AcpAgentError): string {
return dialect.promptErrorDetail?.(error) ?? error.message
return dialect.promptErrorDetail?.(error) ?? acpErrorDataWords(error.data) ?? error.message

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat a blank dialect detail as absent.

If Grok returns data: { message: '', details: 'Quota exceeded' }, its reader returns ''. The ?? expression then skips acpErrorDataWords, so the failure row loses the readable detail. Check that the dialect result is nonblank before selecting it.

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