Skip to content

fix(claude-transcript): skip internal Claude record types in transcripts#206

Open
danljungstrom wants to merge 1 commit into
happier-dev:devfrom
danljungstrom:fix/claude-unsupported-agent-output
Open

fix(claude-transcript): skip internal Claude record types in transcripts#206
danljungstrom wants to merge 1 commit into
happier-dev:devfrom
danljungstrom:fix/claude-unsupported-agent-output

Conversation

@danljungstrom

@danljungstrom danljungstrom commented Jul 22, 2026

Copy link
Copy Markdown

Problem

Sessions were showing [Unsupported agent output] rows in the transcript (see below — two in a row mid tool-run).

Root cause: Claude Code began emitting new JSONL record types around 2026-06-19attachment, last-prompt, mode, pr-link. None are conversation content, but they were missing from INTERNAL_CLAUDE_EVENT_TYPES, so the SDK-to-log converter forwarded them and the UI normalizer rendered each unrecognized output payload as an [Unsupported agent output] placeholder (apps/ui/sources/sync/typesRaw/normalize.ts).

Evidence, counts across one daemon host's ~/.claude/projects/**:

type count earliest in skip-list before
attachment 9897 2026-06-19
last-prompt 5486
mode 2627
pr-link 1811 2026-06-20

attachment subtypes are all context injections (hook output, task reminders, tool/skill listings, file snapshots, queued commands) — no transcript content.

Fix

  1. Add attachment, last-prompt, mode, pr-link to INTERNAL_CLAUDE_EVENT_TYPES. Dropped at read time, feeding the three existing call sites (converter, session scanner, jsonl reader); no new plumbing.
  2. Name the payload type in the UI fallback: [Unsupported agent output: <type>]. The old placeholder discarded data.type, which is why this went unnoticed for a month — nothing to grep for. The next new record type will name itself.

Tests (TDD, RED→GREEN)

  • readClaudeSessionJsonlMessages.test.ts — internal state records dropped, ordinary content kept.
  • sdkToLogConverter.core.test.ts — converter returns null for the four types.
  • normalize.outputInvalidKnownTypeFallback.test.ts — fallback names the unknown type.

Verification

  • @happier-dev/cli typecheck: pass. @happier-dev/app typecheck: pass.
  • CLI src/backends/claude/: 219 files, 1796 tests pass.
  • UI sources/sync/typesRaw/: 34 tests pass.
  • Full UI sources/sync/ lane: 5 failures in syncSessions.*SocketUpdate.test.ts — confirmed pre-existing on dev (identical failure set on a clean-tree run), unrelated to this change.

Out of scope (follow-up)

acceptedPromptTranscriptDiscovery.ts has dead queue-operation/attachment branches — those rows never survive RawJSONLinesSchema (a closed union), so queued-command prompt discovery silently no-ops. Separate bug; worth its own issue.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Note

Skip internal Claude record types (attachment, last-prompt, mode, pr-link) in transcripts

  • Adds attachment, last-prompt, mode, and pr-link to the INTERNAL_CLAUDE_EVENT_TYPES set in internalClaudeEventTypes.ts so these records are filtered out of transcripts and treated as non-conversation content.
  • Updates the unsupported output placeholder in normalize.ts to include the payload's type field in the label, changing [Unsupported agent output] to [Unsupported agent output: <type>] when available.

Macroscope summarized e36bf2d.

Summary by CodeRabbit

  • Bug Fixes
    • Improved Claude transcript handling by excluding additional internal session events and context-injection records from conversation history.
    • Unsupported agent output types now display a clearer placeholder that identifies the unrecognized type.
    • Preserved graceful fallback behavior for unrecognized payloads when the type is missing or empty.
  • Tests
    • Added coverage to ensure internal Claude records and context-injection events are filtered from transcript output.
    • Added coverage for unsupported raw output normalization fallback behavior.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Claude transcript handling now filters additional internal event types, and UI normalization identifies unknown agent output types in fallback text.

Changes

Transcript filtering and output normalization

Layer / File(s) Summary
Claude internal event filtering
apps/cli/src/backends/claude/utils/internalClaudeEventTypes.ts, apps/cli/src/backends/claude/utils/readClaudeSessionJsonlMessages.test.ts, apps/cli/src/backends/claude/utils/sdkToLogConverter.core.test.ts
Adds attachment, last-prompt, mode, and pr-link to the internal event skip list and verifies these records are excluded from transcript output.
Unsupported output labeling
apps/ui/sources/sync/typesRaw/normalize.ts, apps/ui/sources/sync/typesRaw/normalize.outputInvalidKnownTypeFallback.test.ts
Includes an unknown output record’s type in the unsupported-agent-output fallback label and tests the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: leeroybrun

🚥 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 clearly summarizes the main change: skipping internal Claude record types in transcripts.
Description check ✅ Passed The description covers the problem, fix, tests, verification, and follow-up notes, though it does not use the repo's exact template headings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR filters new internal Claude records from transcripts and improves unknown-output diagnostics. The main changes are:

  • Adds four Claude record types to the shared internal-event filter.
  • Covers filtering in the JSONL reader and SDK converter.
  • Includes unknown output types in the UI fallback label.

Confidence Score: 5/5

The changed flow looks mergeable after a small localization cleanup.

  • Internal Claude records are filtered consistently through the shared event set.
  • Focused tests cover the reader, converter, and UI fallback behavior.
  • The new fallback label remains hardcoded in English.

apps/ui/sources/sync/typesRaw/normalize.ts

Important Files Changed

Filename Overview
apps/cli/src/backends/claude/utils/internalClaudeEventTypes.ts Adds four non-conversational Claude record types to the shared internal-event filter.
apps/cli/src/backends/claude/utils/readClaudeSessionJsonlMessages.test.ts Confirms the JSONL reader drops the new internal records while retaining conversation content.
apps/cli/src/backends/claude/utils/sdkToLogConverter.core.test.ts Confirms the SDK converter returns no transcript output for each new internal record type.
apps/ui/sources/sync/typesRaw/normalize.ts Adds the unknown payload type to the fallback transcript label, but leaves the user-visible template untranslated.
apps/ui/sources/sync/typesRaw/normalize.outputInvalidKnownTypeFallback.test.ts Verifies that the fallback label includes an unknown output type.

Reviews (1): Last reviewed commit: "fix(claude-transcript): skip internal Cl..." | Re-trigger Greptile

Comment on lines +759 to +761
const unsupportedLabel = typeof unsupportedType === 'string' && unsupportedType.length > 0
? `[Unsupported agent output: ${unsupportedType}]`
: '[Unsupported agent output]';

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.

P2 Fallback Label Bypasses Localization

When an unknown output type reaches this branch, the transcript displays a new hardcoded English label. Non-English users receive untranslated diagnostic text; use a translated template with the record type passed as an interpolation value.

Context Used: apps/ui/CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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

🧹 Nitpick comments (2)
apps/cli/src/backends/claude/utils/sdkToLogConverter.core.test.ts (1)

393-398: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use narrow, justified test fixture typing.

These changed tests introduce broad any usage without documenting why. Keep only narrowly required boundary casts with a one-line rationale, and remove the unnecessary annotation from the UI fixture.

  • apps/cli/src/backends/claude/utils/sdkToLogConverter.core.test.ts#L393-L398: justify or replace sdkMessage as any.
  • apps/cli/src/backends/claude/utils/sdkToLogConverter.core.test.ts#L401-L405: justify or replace the attachment as any cast.
  • apps/ui/sources/sync/typesRaw/normalize.outputInvalidKnownTypeFallback.test.ts#L67-L79: remove const raw: any and rely on inferred typing.

As per coding guidelines, broad any casts are forbidden except in narrow boundary fixtures/harnesses with a one-line justification.

🤖 Prompt for 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.

In `@apps/cli/src/backends/claude/utils/sdkToLogConverter.core.test.ts` around
lines 393 - 398, Replace the broad sdkMessage as any cast in
sdkToLogConverter.core.test.ts:393-398 with a narrow justified boundary cast,
adding a one-line rationale if the cast is required; apply the same treatment to
the attachment as any cast at sdkToLogConverter.core.test.ts:401-405. In
normalize.outputInvalidKnownTypeFallback.test.ts:67-79, remove const raw: any
and rely on inferred typing.

Source: Coding guidelines

apps/ui/sources/sync/typesRaw/normalize.ts (1)

756-761: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Keep the fallback label in the translation layer.

This new user-visible string is hardcoded in the normalization layer. Format it with t(...) and interpolation, or carry the unsupported type as metadata and localize the label at the rendering boundary.

As per coding guidelines, all user-visible strings, accessibility labels, and placeholders must use t(...) and be added to every locale.

Also applies to: 771-771

🤖 Prompt for 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.

In `@apps/ui/sources/sync/typesRaw/normalize.ts` around lines 756 - 761, Localize
the user-visible fallback labels created in the normalization flow around
unsupportedType and unsupportedLabel. Move the label formatting to the
translation layer or carry the unsupported type as metadata to the rendering
boundary, then use t(...) with interpolation and add the translation key to
every locale; apply the same change to the related fallback at the additional
referenced location.

Source: Coding guidelines

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

Nitpick comments:
In `@apps/cli/src/backends/claude/utils/sdkToLogConverter.core.test.ts`:
- Around line 393-398: Replace the broad sdkMessage as any cast in
sdkToLogConverter.core.test.ts:393-398 with a narrow justified boundary cast,
adding a one-line rationale if the cast is required; apply the same treatment to
the attachment as any cast at sdkToLogConverter.core.test.ts:401-405. In
normalize.outputInvalidKnownTypeFallback.test.ts:67-79, remove const raw: any
and rely on inferred typing.

In `@apps/ui/sources/sync/typesRaw/normalize.ts`:
- Around line 756-761: Localize the user-visible fallback labels created in the
normalization flow around unsupportedType and unsupportedLabel. Move the label
formatting to the translation layer or carry the unsupported type as metadata to
the rendering boundary, then use t(...) with interpolation and add the
translation key to every locale; apply the same change to the related fallback
at the additional referenced location.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 21f8b67e-4473-454a-bfdd-cb307916abb3

📥 Commits

Reviewing files that changed from the base of the PR and between 4766022 and e71772f.

📒 Files selected for processing (5)
  • apps/cli/src/backends/claude/utils/internalClaudeEventTypes.ts
  • apps/cli/src/backends/claude/utils/readClaudeSessionJsonlMessages.test.ts
  • apps/cli/src/backends/claude/utils/sdkToLogConverter.core.test.ts
  • apps/ui/sources/sync/typesRaw/normalize.outputInvalidKnownTypeFallback.test.ts
  • apps/ui/sources/sync/typesRaw/normalize.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 22, 2026
Claude Code began emitting `attachment`, `last-prompt`, `mode`, and `pr-link`
records around 2026-06-19. None are conversation content, but they were not in
INTERNAL_CLAUDE_EVENT_TYPES, so the SDK-to-log converter forwarded them and the
UI normalizer rendered each one as an "[Unsupported agent output]" row.

Add the four types to the skip list, and name the payload type in the UI
fallback placeholder. The old placeholder discarded `data.type`, which is why
this went unnoticed for a month: there was nothing to grep for.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@danljungstrom

Copy link
Copy Markdown
Author

Thanks @coderabbitai — went through both.

1. any in test fixtures — applied. Added one-line rationale comments above the boundary casts in sdkToLogConverter.core.test.ts. These are genuine boundary casts: converter.convert() takes the typed SDKMessage union and the fixtures are raw internal records outside it by design (the pre-existing rate_limit_event test uses the same pattern). Left const raw: any in normalize.outputInvalidKnownTypeFallback.test.ts as-is — the two pre-existing tests in that file use the same annotation and normalizeRawMessage's typed param won't accept the partial fixture without it.

2. i18n fallback label — declining, out of scope. normalize.ts is the sync/data layer with zero t() usage; the sibling placeholder '[Unsupported transcript record]' (line ~1183, untouched) is hardcoded too, as was [Unsupported agent output] before this PR. The string is a diagnostic carrying a raw record-type name, not user prose. Localizing only the line I touched would be inconsistent with its sibling. If these placeholders should be localized, that's a separate coherent change covering both — happy to file it.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@danljungstrom Thanks—those explanations make sense.

  • The boundary-cast comments appropriately document why the raw internal-record fixtures must cross the SDKMessage type boundary, and retaining the existing local any convention for the partial normalization fixtures is reasonable.
  • Agreed on the fallback label: localizing only this diagnostic string would be inconsistent with the existing hardcoded sibling placeholder. I’m happy to defer that broader localization work to a dedicated follow-up.

Would you like me to open a GitHub issue for that follow-up?

🐇


✏️ Learnings added
Learnt from: danljungstrom
URL: https://github.com/happier-dev/happier/pull/206

Timestamp: 2026-07-22T07:18:17.926Z
Learning: In the UI sync/data layer, `apps/ui/sources/sync/typesRaw/normalize.ts` deliberately uses hardcoded diagnostic fallback placeholders rather than `t()`; `[Unsupported agent output: <type>]` is consistent with the existing `[Unsupported transcript record]` placeholder. Any localization should be a separate cohesive change that covers related diagnostic placeholders together.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@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/ui/sources/sync/typesRaw/normalize.ts`:
- Around line 756-761: Update the unsupported type handling near unsupportedType
and unsupportedLabel to trim string values before checking whether they are
non-empty and before interpolating them. Treat whitespace-only types as missing,
while using the trimmed value for padded types; add regression coverage for both
cases.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7981a99e-1b55-4bf0-b2ee-02a68260b476

📥 Commits

Reviewing files that changed from the base of the PR and between e71772f and e36bf2d.

📒 Files selected for processing (5)
  • apps/cli/src/backends/claude/utils/internalClaudeEventTypes.ts
  • apps/cli/src/backends/claude/utils/readClaudeSessionJsonlMessages.test.ts
  • apps/cli/src/backends/claude/utils/sdkToLogConverter.core.test.ts
  • apps/ui/sources/sync/typesRaw/normalize.outputInvalidKnownTypeFallback.test.ts
  • apps/ui/sources/sync/typesRaw/normalize.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/cli/src/backends/claude/utils/internalClaudeEventTypes.ts
  • apps/cli/src/backends/claude/utils/sdkToLogConverter.core.test.ts
  • apps/cli/src/backends/claude/utils/readClaudeSessionJsonlMessages.test.ts
  • apps/ui/sources/sync/typesRaw/normalize.outputInvalidKnownTypeFallback.test.ts

Comment on lines +756 to +761
// Name the payload type: agent CLIs keep adding record types, and an unnamed placeholder
// leaves nothing to grep for when one starts leaking into transcripts.
const unsupportedType = (raw.content.data as { type?: unknown }).type;
const unsupportedLabel = typeof unsupportedType === 'string' && unsupportedType.length > 0
? `[Unsupported agent output: ${unsupportedType}]`
: '[Unsupported agent output]';

Copy link
Copy Markdown

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 whitespace-only types as missing.

length > 0 accepts values such as ' ', producing an unhelpful placeholder, and preserves surrounding whitespace for padded types. Trim the value before checking and interpolating it, with regression cases for whitespace-only and padded types.

Also applies to: 771-771

🤖 Prompt for 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.

In `@apps/ui/sources/sync/typesRaw/normalize.ts` around lines 756 - 761, Update
the unsupported type handling near unsupportedType and unsupportedLabel to trim
string values before checking whether they are non-empty and before
interpolating them. Treat whitespace-only types as missing, while using the
trimmed value for padded types; add regression coverage for both cases.

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