Skip to content

fix(claude): handle all SDK stream messages; stop spurious work-log warning rows#4244

Merged
t3dotgg merged 4 commits into
mainfrom
t3code/claude-sdk-event-coverage
Jul 22, 2026
Merged

fix(claude): handle all SDK stream messages; stop spurious work-log warning rows#4244
t3dotgg merged 4 commits into
mainfrom
t3code/claude-sdk-event-coverage

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Jul 22, 2026

Copy link
Copy Markdown
Member

What

Every Claude SDK stream message type is now explicitly handled in the ClaudeAdapter, with type-level exhaustiveness guards so future SDK additions fail typecheck instead of silently degrading.

Why now (split out of #4220)

Users on main see spurious error-toned rows in the work log today:

  • api_retry storms: a 502 burst renders as ten orange "Claude system message 'api_retry'" rows (one per attempt). Now maps to a session.state.changed heartbeat (reason: api_retry:3/10); the terminal error path already reports the real failure once.
  • background_tasks_changed: an undeclared-but-real wire subtype (absent from the SDK's typed union) that rendered as "Claude system message 'background_tasks_changed' (no displayable text content)". Now consumed silently.

Found via a log-driven audit: censused every native SDK message in captured provider NDJSON logs from real sessions, cross-referenced against the SDK 0.3.170 union (33 message types / 24 system subtypes).

Changes

  • api_retry → session heartbeat, not N warning rows
  • session_state_changed → authoritative session.state.changed mapping (idle/running/requires_action)
  • notification → warning row only for high/immediate priority
  • background_tasks_changed (undeclared), task_updated, model_refusal_fallback, local_command_output, plugin_install, commands_changed, memory_recall, elicitation_complete, prompt_suggestion → deliberately consumed, no spurious rows
  • Both default branches now narrow to never (message satisfies never): a new SDK message type or subtype is a typecheck failure at upgrade time; the runtime fallback still catches undeclared wire-only subtypes

Verification

  • New test: emits undeclared + UX-internal subtypes and an api_retry, asserts zero warning rows and the heartbeat event
  • 60 ClaudeAdapter tests pass; tsgo --noEmit clean for apps/server

Note for #4220: that branch carries a richer task_updated mapping (agent status patches); this PR only stops it warning. #4220 will absorb this cleanly on rebase.

🤖 Generated with Claude Code


Note

Medium Risk
Changes runtime event mapping for live Claude sessions (session state, warnings, retries); behavior is well-covered by tests but affects user-visible work logs during provider outages.

Overview
Stops false error-toned work-log rows by explicitly handling Claude SDK system and top-level messages that previously fell through to unknown-subtype warnings.

api_retry now emits a session.state.changed heartbeat (reason: api_retry:n/m) instead of one warning per retry during 502 storms. session_state_changed maps to session.state.changed (running / waiting / ready). notification only surfaces as runtime.warning when priority is high or immediate.

Wire-only or UX-internal traffic (background_tasks_changed, task_updated, commands_changed, memory_recall, prompt_suggestion, and similar) is consumed silently. Router default branches use message satisfies never so new SDK union members fail typecheck at upgrade; undeclared wire subtypes still hit the runtime fallback.

Adds an integration test that asserts a single high-priority notification warning, correct session-state mapping, and an api_retry heartbeat.

Reviewed by Cursor Bugbot for commit 6a5718d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix Claude SDK stream handling to suppress spurious work-log warning rows

  • Adds silent handling in handleSystemMessage for several SDK system subtypes (background_tasks_changed, task_updated, commands_changed, model_refusal_fallback, local_command_output, plugin_install, memory_recall, elicitation_complete) that previously generated spurious warning rows.
  • Maps session_state_changed messages to session.state.changed events with normalized states (running, waiting, ready) and emits a heartbeat session.state.changed for api_retry messages.
  • Limits notification-driven warnings to high or immediate priority only; lower-priority notifications are silently consumed.
  • Adds top-level handling for prompt_suggestion in handleSdkMessage so it no longer surfaces as an unknown-type warning.
  • Unknown declared subtypes and top-level types still emit a runtime warning via a TypeScript exhaustiveness guard.

Macroscope summarized 6a5718d.

Summary by CodeRabbit

  • Bug Fixes

    • Reduced unnecessary warning messages for internal and unsupported Claude system events.
    • Improved session status updates for retries and session state changes.
    • High- and immediate-priority notifications continue to surface as warnings, while lower-priority notifications remain unobtrusive.
    • Added support for safely handling prompt suggestions and task-related events without disrupting sessions.
  • Tests

    • Added coverage confirming internal events do not generate warning rows and retry activity updates session state correctly.

t3dotgg and others added 2 commits July 21, 2026 19:15
The SDK stream emits this roster snapshot but its subtype is absent from
the typed union, so it fell through to the unknown-subtype warning path
and rendered as a spurious error row ('no displayable text content') in
client work logs. The task_* lifecycle events carry the authoritative
per-agent data; the typed background_tasks control request remains the
reconciliation source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s guards

Log-driven audit of real captured traffic (verification runs + wire
probes) found api_retry storms rendering as 10 warning rows per 502 and
several union subtypes falling into the unknown-subtype warning path.

- api_retry → session.state.changed heartbeat (terminal error path
  already reports the real failure)
- session_state_changed → authoritative session.state.changed mapping
- notification → warning row only for high/immediate priority
- model_refusal_fallback, local_command_output, plugin_install,
  commands_changed, memory_recall, elicitation_complete,
  prompt_suggestion → deliberately consumed, no spurious rows
- default branches now narrow to never: a new SDK message type or
  subtype fails typecheck instead of silently warning at runtime; the
  runtime fallback still catches undeclared wire-only subtypes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 is concise and accurately summarizes the main change: handling Claude SDK stream messages and reducing warning rows.
Description check ✅ Passed The PR description covers what changed, why, and verification, though it uses custom headings instead of the template's exact section names.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/claude-sdk-event-coverage

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

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Jul 22, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 22, 2026
@macroscopeapp

macroscopeapp Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved

This is a straightforward bug fix that handles SDK stream messages that were previously falling through to an unknown-subtype warning path, causing spurious error rows in client work logs. The scope is limited to message handling in ClaudeAdapter with comprehensive test coverage.

You can customize Macroscope's approvability policy. Learn more.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/src/provider/Layers/ClaudeAdapter.test.ts`:
- Around line 1729-1779: Extend the test around the existing runtime message
emissions to cover the missing dispatch branches: task_updated, all remaining
silent subtypes, prompt_suggestion, high and immediate notifications, and
session_state_changed. Add focused table-driven assertions that verify silent
messages produce no warnings and the other messages produce their expected
runtime events, while preserving the existing api_retry heartbeat assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 29456e95-069d-480c-ac7f-4b98becbea79

📥 Commits

Reviewing files that changed from the base of the PR and between 62cf461 and 45251ac.

📒 Files selected for processing (2)
  • apps/server/src/provider/Layers/ClaudeAdapter.test.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.ts

Comment thread apps/server/src/provider/Layers/ClaudeAdapter.test.ts Outdated
Table-driven emission of every silent subtype, task_updated,
prompt_suggestion, high-priority notification (asserts exactly one
warning row), and all three session_state_changed mappings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp
macroscopeapp Bot dismissed their stale review July 22, 2026 02:40

Dismissing prior approval to re-evaluate 057a5c8

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 22, 2026
Assert (message satisfies never) and cast as separate statements,
mirroring orchestration/decider.ts:778 — same compile-time guarantee,
house style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp
macroscopeapp Bot dismissed their stale review July 22, 2026 02:50

Dismissing prior approval to re-evaluate 6a5718d

@t3dotgg
t3dotgg merged commit 6f34ad3 into main Jul 22, 2026
16 checks passed
@t3dotgg
t3dotgg deleted the t3code/claude-sdk-event-coverage branch July 22, 2026 02:58
t3dotgg added a commit that referenced this pull request Jul 22, 2026
…4244

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant