Repository navigation
fix(claude): an informational note is a warning row in its own words, never a raw frame row - #24471
Conversation
… never a raw frame row Claude Code shows info, notice and suggestion notes as transcript chrome; only a warning earns a row. The frame used to fall through to the provider fallback and print its opcode.
|
Review status: ready for review. Two independent reviewers, a release-readiness check and live QA are done. The problem. Claude Code sends Orca short What changes for the user. When a hook blocks or stops a prompt, the chat shows one amber warning row with Claude Code's own explanation. It stays visible after the turn settles, so the user can see why nothing happened. Other notes ( Fixed during review (1e52f36, text only; runtime unchanged):
Deferred (minor, not blocking):
Verified
Not verified
|
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Dedicated informational row — new
claude-informational-row.tsmaps asystem/informationalframe to a singlestatus/warningrow carrying the frame's own boundedcontent, and to nothing forinfo,notice,suggestion, or a blankwarning. - Fallback interception —
claude-structured-provider-fallback.tshandles the informational kind before the genericunhandledProviderFrameJournalItem, so the rawclaude · message:system:informationalopcode row can never be written. - Disposition reclassification —
message:system:informationalmoves fromtimeline-substantivetostatus-chromeas defense-in-depth. - Test coverage — new
claude-informational-row.test.tsdrives real frames through the real translator and journal, asserting exact bodies (no row forinfo/notice/suggestionand blankwarning; exactly{ kind: 'status', tone: 'warning', text: content }forwarning).
I confirmed the root cause in unhandled-provider-frame.ts:36 — MESSAGE_KEYS omits content, so the pre-fix generic row had no sentence and fell through to the opcode string. The new branch is reached before classification, and the only consumer of classifyProviderFrame is the same generic fallback, so the status-chrome change cannot drop the intended warning row. Ran the new test plus provider-frame-disposition.test.ts (29 tests, all pass) and oxlint on all four changed files (clean).
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (4)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughClaude informational frames are classified as status chrome. A new row-body function returns bounded, trimmed text for warning-level messages with nonblank content. The fallback appends and publishes a stamped warning row when the function returns a body. Otherwise, it returns without opening a turn. Tests cover Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to Claude warnings now display their message content, while other informational frames remain out of chat. No actionable merge-blocking risk remains in the supplied evidence; merge after normal checks. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Description checkExplanation The description is detailed and covers the user impact, implementation, rationale, visual proof, testing, and compatibility. It fails the repository requirement because the mandatory Linked Issue section says no issue, and the required pnpm lint, typecheck, test, and build checklist item remains unchecked. Resolution Link the actual issue addressed, such as the relevant pre-existing issue, in the Linked Issue section using the required issue reference. Update the final checklist item to reflect the actual status of pnpm lint, pnpm typecheck, pnpm test, and pnpm build, or state that CI will cover any commands not run locally.
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
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. Comment |

ELI5
Claude Code sometimes sends Orca a short note about the session, such as a hook's message or a prompt hook refusing a prompt. Orca didn't recognise that kind of message, so it showed the raw protocol name in the chat as a row reading "claude · message:system:informational". Now a warning shows as a warning, in its own words, and the other notes add no row to the chat.
What Changed
The problem. After Claude's API refused a prompt and the turn was stopped, the chat showed a row reading "› claude message:system:informational" (QA on the M4 Air, on main). Claude Code sends
system/informationalframes:{ type: 'system', subtype: 'informational', content, level }, where the level isinfo,notice,suggestionorwarning. Orca had no translator for this kind and classed it as timeline content, so the generic provider-frame fallback wrote it as a row named after its opcode. The frame keeps its words incontent, which the fallback doesn't read, so the row had nothing else to show. This is the same class as #23704's raw Codexthread/statusrow.Before / After
informationalatinfo,noticeorsuggestion(e.g. a hook'ssystemMessage, which arrives as anotice)informationalatwarning(e.g. a UserPromptSubmit hook that blocked the prompt)warningwith no wordsMechanism
claude-informational-row.ts(new, besideclaude-api-retry-row.ts) turns a frame into its row: awarningwith words gives astatusrow withtone: 'warning'and the boundedcontent; anything else gives none.api_retryrow.provider-frame-disposition.tsclassifiesmessage:system:informationalasstatus-chromeinstead oftimeline-substantive, so no path can render it as a raw frame.Why
The Claude Agent SDK's type for this frame (
SDKInformationalMessagein@anthropic-ai/claude-agent-sdk'ssdk.d.ts) documents the levels:infoshows only in transcript mode,noticerenders in inactive gray, andsuggestionandwarningare more prominent. In a chat view the common pattern adds no row forinfo,noticeorsuggestion, and Orca does the same. Orca shows awarningin the frame's own words, because a warning is what explains why a prompt did nothing. That reuses Orca's existing typed-row pattern for Claude system frames (api_retry), not a new mechanism.Alternatives considered:
contentfor every level. That turns every hook message and session note into a chat row.Differences from the common pattern
warningrow visible after the turn settles; in the common pattern it is not visible once the turn settles. A warning explains why a prompt did nothing, so it should not need expanding to find.warningwrites no row, since it has nothing to say.One difference from Claude Code's own terminal, also intended: a hook's
systemMessage(anotice) adds no chat row, where Claude Code's terminal shows it dimmed (per the SDK type above). This follows the common pattern for a chat view; such a note does not change the turn, and Claude's reply still shows.Linked Issue
No issue. Found in the M4 Air QA of #24127, on main.
Visual Proof
The row before the fix, on main:
Live QA on a second Mac, comparing this branch with current main, each running in an isolated test copy of Orca with its own home folder and real Claude Code 2.1.287. A prompt hook in the test repo blocks, stops, or annotates a prompt on demand, so each frame is real, not faked. This run was scripted, with no AI agent driving it, because no agent driver was available on that machine.
A prompt hook blocks the prompt (


warning). Before, on main: the raw row.After: one warning row in Claude Code's own words. It stays visible after the turn settles, with nothing expanded.
A prompt hook stops the prompt (


warning, "Operation stopped by hook"). Before, then after:A hook adds a message (


notice), then Claude answers. Before, on main, the note appears as raw rows; the hook there fired twice because of a test-script quoting slip:After: no extra row, and Claude's reply "ok" shows as usual.
A normal message after a blocked one, and after quitting and reopening the app. The reply arrives, and each warning still shows once:


Testing
I manually tested these changes (live QA on a second Mac, see Visual Proof)
Automated tests added/updated, or explained why not below
New
claude-informational-row.test.ts, which drives frames shaped as Claude Code 2.1.280 sends them through the real Claude journal translator and journal:info,noticeandsuggestionwrite no row;warningwrites exactly{ kind: 'status', tone: 'warning', text: <content> };warningwrites no row.All 5 failed on main before the fix: each wrote the raw "claude · message:system:informational" row.
Review round: the new test file, the api_retry row, Claude journal translation, provider-frame classification and unhandled-frame tests pass (71 tests);
tc:nodeand oxlint pass on the final head.Every
src/main/claudetest file (123, the real-CLI test excluded) and the provider-frame classification, fallback and activity tests pass: 1,346 + 89 tests.tc:node, oxlint andcheck:code-quality:changedpass.AI Disclosure
Review
Reviewed by two independent reviewers plus a release-readiness pass; see the status comment.
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
The change happens where the Claude child runs, so it is the same on macOS, Linux, Windows and SSH hosts. Nothing on the wire changes; older clients see the same rows as new ones, since the host writes them.
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)Author: @BrennanKB5