Skip to content

fix(claude): an informational note is a warning row in its own words, never a raw frame row - #24471

Merged
brennanb2025 merged 2 commits into
mainfrom
brennanb2025/claude-informational-row
Oct 2, 2026
Merged

brennanb2025 merged 2 commits into
mainfrom
brennanb2025/claude-informational-row

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​78 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​78
Prod 3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​46 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​45

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/informational frames: { type: 'system', subtype: 'informational', content, level }, where the level is info, notice, suggestion or warning. 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 in content, which the fallback doesn't read, so the row had nothing else to show. This is the same class as #23704's raw Codex thread/status row.

Before / After

Frame Before After
informational at info, notice or suggestion (e.g. a hook's systemMessage, which arrives as a notice) a raw "claude · message:system:informational" row no row
informational at warning (e.g. a UserPromptSubmit hook that blocked the prompt) the same raw row one warning row with the frame's own words
a warning with no words the raw row no row

Mechanism

  • claude-informational-row.ts (new, beside claude-api-retry-row.ts) turns a frame into its row: a warning with words gives a status row with tone: 'warning' and the bounded content; anything else gives none.
  • The Claude provider fallback writes that row for this kind and never the generic opcode row. This is the same place and shape as the api_retry row.
  • provider-frame-disposition.ts classifies message:system:informational as status-chrome instead of timeline-substantive, so no path can render it as a raw frame.

Why

The Claude Agent SDK's type for this frame (SDKInformationalMessage in @anthropic-ai/claude-agent-sdk's sdk.d.ts) documents the levels: info shows only in transcript mode, notice renders in inactive gray, and suggestion and warning are more prominent. In a chat view the common pattern adds no row for info, notice or suggestion, and Orca does the same. Orca shows a warning in 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:

  • Lead the fallback row with content for every level. That turns every hook message and session note into a chat row.
  • Hide every level. That would also drop a warning such as a hook blocking the prompt, which is the only explanation of why nothing happened.

Differences from the common pattern

  • Intended: Orca keeps the warning row 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.
  • Intended: a blank warning writes no row, since it has nothing to say.
  • Intended: the raw frame is not stored with the row; Orca has no diagnostics view to read it from, and the row already carries the frame's words.

One difference from Claude Code's own terminal, also intended: a hook's systemMessage (a notice) 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:

Before: raw claude message:system:informational row

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.
Before: blocked prompt shows the raw claude message:system:informational row
After: one warning row in Claude Code's own words. It stays visible after the turn settles, with nothing expanded.
After: blocked prompt shows a warning row with the hook's reason

A prompt hook stops the prompt (warning, "Operation stopped by hook"). Before, then after:
Before: stopped prompt shows raw rows
After: stopped prompt shows a warning row

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:
Before: notice shows raw rows above the reply
After: no extra row, and Claude's reply "ok" shows as usual.
After: no notice row, reply shown

A normal message after a blocked one, and after quitting and reopening the app. The reply arrives, and each warning still shows once:
After: follow-up answered
After: reopened chat keeps each warning 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, notice and suggestion write no row;
    • warning writes exactly { kind: 'status', tone: 'warning', text: <content> };
    • a blank warning writes 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:node and oxlint pass on the final head.

  • Every src/main/claude test file (123, the real-CLI test excluded) and the provider-frame classification, fallback and activity tests pass: 1,346 + 89 tests.

  • tc:node, oxlint and check:code-quality:changed pass.

AI Disclosure

Review

Reviewed by two independent reviewers plus a release-readiness pass; see the status comment.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and 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

  • 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)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

Author: @BrennanKB5

… 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.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status: ready for review. Two independent reviewers, a release-readiness check and live QA are done.

The problem. Claude Code sends Orca short system/informational notes, for example when a prompt hook blocks a prompt, or a hook adds a message. Orca had no handler for them, so the chat showed a raw row reading "› claude message:system:informational", with no words.

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 (info, notice such as a hook's message, suggestion) add no chat row, and Claude's reply shows as usual. The raw protocol row is gone.

Fixed during review (1e52f36, text only; runtime unchanged):

  • The code comment, test and PR body named a Stop hook as the source of the warning; a Stop hook sends no such note. They now name a UserPromptSubmit hook that blocked the prompt, which is the real source and what the test now uses.
  • They said Claude Code hides all non-warning notes; it hides only info. The PR body now lists "a hook's message adds no chat row, though Claude Code's terminal shows it dimmed" as an intended difference.

Deferred (minor, not blocking):

  • The status-chrome classification change is never reached on the Claude path, because the new branch returns first, and no test covers it. The existing api_retry row works the same way.
  • The new fallback branch repeats a few lines of the generic one.
  • A warning that arrives during a running turn (for example Claude Code's "auto mode is unavailable … so Claude stopped") folds behind "Worked for N" with the rest of the turn, like other warning rows. That is not a regression, since main folded a raw row there. The blocked-prompt warning is not affected: it arrives before any turn opens and stays visible, confirmed live.
  • After quitting and reopening, one blocked prompt lost its "Worked for 0s" line. This is cosmetic, was not compared against main and was not investigated.

Verified

  • Live QA on a second Mac (M4 Air), this branch against current main, in isolated test copies of Orca with real Claude Code 2.1.287:
    • The QA was scripted, with no AI agent driving it, because no agent driver was available on that machine.
    • A prompt hook produced real frames: a blocked prompt, a stopped prompt, and a hook message.
    • Main showed the raw row in each case. This branch showed a warning row for the first two and no row for the hook message.
    • A follow-up message and a quit-and-reopen behaved normally. Screenshots are in the PR body.
  • The 5 new tests fail on main and pass on this branch.
  • 71 tests pass across the related translation, classification and fallback files.
  • Typecheck (node) and lint pass; CI is green.

Not verified

  • An info-level note, which has no reliable trigger.
  • Codex, which this change does not touch.
  • Mobile was checked by reading the code (it renders text status rows), not run live.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 2, 2026 00:00
@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Changes how Claude warning messages appear in chat history.

The PR appears safe to merge; no actionable regression was established.

Summary

The PR replaces raw Claude informational-frame rows with a warning row containing the frame’s text, while omitting lower-level notes and blank warnings.

  • Adds journal-level tests for each supported level and blank warning content.
  • Reclassifies informational frames as status chrome so the generic row is not used.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Claude informational frame] --> B{Warning with text?}
  B -- Yes --> C[Bound text and append warning status row]
  B -- No --> D[Append no row]
  C --> E[Journal and chat]
Loading

Reviews (1) · Last reviewed commit: "fix(claude): name the real source of an ..."

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • Dedicated informational row — new claude-informational-row.ts maps a system/informational frame to a single status/warning row carrying the frame's own bounded content, and to nothing for info, notice, suggestion, or a blank warning.
  • Fallback interception — claude-structured-provider-fallback.ts handles the informational kind before the generic unhandledProviderFrameJournalItem, so the raw claude · message:system:informational opcode row can never be written.
  • Disposition reclassification — message:system:informational moves from timeline-substantive to status-chrome as defense-in-depth.
  • Test coverage — new claude-informational-row.test.ts drives real frames through the real translator and journal, asserting exact bodies (no row for info/notice/suggestion and blank warning; exactly { kind: 'status', tone: 'warning', text: content } for warning).

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).

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

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 configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 90a782cd-5411-4519-9357-d5f4783a76ad

📥 Commits

Reviewing files that changed from the base of the PR and between 38c2d1d and 1e52f36.

📒 Files selected for processing (4)
  • src/main/claude/claude-informational-row.test.ts
  • src/main/claude/claude-informational-row.ts
  • src/main/claude/claude-structured-provider-fallback.ts
  • src/main/native-chat/agent-session-wire/provider-frame-disposition.ts

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


📝 Walkthrough

Walkthrough

Claude 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 info, notice, and suggestion frames, warning content, and whitespace-only warning content.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 1e52f

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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning 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 sect… 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, …
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly identifies the Claude informational-frame behavior change: warning rows use the frame content and raw frame rows are avoided.
Full details: Description check

Explanation

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.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

@brennanb2025
brennanb2025 merged commit 9dea72f into main Oct 2, 2026
60 checks 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.

1 participant