[FEATURE] Stable, documented schema for ~/.claude/projects/<encoded-cwd>/<uuid>.jsonl line types

Status Open
Maintainer reply None cached
Activity 8 comments · opened Apr 26, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Context

We are building a sibling VS Code / Cursor / Windsurf extension (aims.dashboard) that runs alongside the official anthropic.claude-code extension. The extension provides a four-pane operational shell over Claude Code: a persistent always-on chat pane that we drive ourselves through the Claude Agent SDK, and a per-task tab pane that is owned by the official extension. For the per-task pane we cannot use SDK in-process hooks, because we did not spawn those sessions; the official extension did. Our only viable observation channel for those sessions is to file-watch the append-only JSONL transcripts under ~/.claude/projects/<encoded-cwd>/<uuid>.jsonl.

Direct inspection of extension.js v2.1.119 confirms there is no public VS Code API exported from the official extension, no extensionDependencies hook, no command surface for observing session state, and the IDE MCP bridge under ~/.claude/ide/<port>.lock exposes driver-side tools (manipulate the IDE) rather than observer-side events (watch what the CLI is doing). The JSONL transcript is the only durable, cross-process observation channel for sessions we did not spawn ourselves.

Several community dashboards already depend on this surface (examples in the references section). We expect more to appear as VS Code 1.109's multi-agent positioning takes hold.

What we observed

A spot-check of current local transcripts on Claude Code v2.1.118 / v2.1.119 surfaces the following top-level type values in a single mid-length project session:

agent-name
assistant
attachment
custom-title
file-history-snapshot
last-prompt
permission-mode
queue-operation
system
user

type=system carries a subtype field. The values we have observed are:

away_summary
bridge_status
compact_boundary
local_command
scheduled_task_fire
stop_hook_summary
turn_duration

When the CLI is launched with --include-hook-events, we additionally see system/hook_callback lines.

Several of these (agent-name, custom-title, queue-operation, local_command, scheduled_task_fire, pr-link, system/bridge_status) are recent additions that did not appear in older transcripts and are not described in the public docs. We have no way to know whether new types are additive (safe) or whether existing types may be renamed or removed (breaking).

Why I think this benefits Anthropic

  • More durable third-party integrations. A documented and stable schema lets the ecosystem of dashboards, statusline scripts, and observability tools track Claude Code releases without breaking on every upgrade.
  • Reduced support load. Tool-builders today reverse-engineer the schema from minified extension.js and a sample of live transcripts. A single documentation page absorbs the questions that currently end up on issues, Discord, and community blog posts.
  • Aligns with VS Code 1.109 multi-agent direction. The official editorial line ("running Claude and Codex agents directly alongside Copilot is the future") implies a richer ecosystem of co-resident extensions. A stable observation contract is a load-bearing piece of that ecosystem.
  • Existing community dependence. Several community dashboards already parse the format (jspw/Claude-Code-Dashboard, nateherkai/token-dashboard, withLinda/claude-JSONL-browser, phuryn/claude-usage, statusline scripts via ccusage and ClaudeCodeStatusLine). Documenting the contract recognises a surface the ecosystem is already treating as public.

Proposed Solution

We would like one or more of the following:

  1. Documented schema for each line type and system.subtype value. A single page (e.g. under code.claude.com/docs/en/) listing each type/subtype and its required and optional fields. This need not be exhaustive; the core types (user, assistant, system/*, attachment, permission-mode) cover most observer use cases.
  1. A stability commitment. A statement of the form "additions are safe at any minor release; removals or renames are announced in the changelog one minor release ahead." Even a SemVer-style versioning of the transcript schema (separate from the CLI SemVer if necessary) would be enough for tool-builders to pin a known-good schema and detect drift.
  1. Changelog entry per minor release listing JSONL additions or changes. The current changelog at anthropics/claude-agent-sdk-typescript and the Claude Code release notes do not mention JSONL line-type changes. A one-line note when a new type or system.subtype is introduced would let tool-builders update their parsers in step.

Alternative Solutions

A public observation API. If the schema is too volatile to commit to, an alternative is a public event surface -- either via the existing IDE MCP bridge (additional MCP tools whose role is observation, e.g. mcp__ide__subscribeToolEvents), via a published cross-extension API on the official VS Code extension (an EventEmitter returned from activate()), or via a Language Model Chat Participant interface that other extensions can subscribe to. Any of these would let us drop the file-watch path entirely.

Priority

Critical - Blocking my work

Feature Category

Developer tools/SDK

Use Case Example

Use case

aims.dashboard provides a four-pane operational shell:

  • Pane 1 (always-on CoS chat): SDK-driven; we own these sessions and use the SDK's in-process hook surface (SessionStart, PreToolUse, PostToolUse, Notification, Stop, PreCompact). This pane is unaffected by JSONL schema changes.
  • Pane 2 (per-task session tabs): owned by the official extension via the URI handler vscode://anthropic.claude-code/open?prompt=...&session=.... We file-watch the JSONL to surface tool-call status, permission prompts, and session-end signals in a sidebar tray. This pane breaks silently when an unknown type or new required field appears.
  • Pane 3 (status tray): Supabase-backed projection of state across cockpits. Receives events from both Pane 1 (SDK callbacks) and Pane 2 (JSONL tail).
  • Pane 4 (preview): unaffected.

The hybrid model lets us keep the polished UX of the official extension for the per-task pane (auto-accept diffs, native diff viewer, plan-mode markdown editing, sessions list, checkpoints) while still surfacing per-tool-call activity to operators in Pane 3 within a sub-second budget.

If the JSONL schema is volatile, our Pane 2 observability degrades silently on every CLI upgrade. We currently mitigate this by pinning the CLI version via pathToClaudeCodeExecutable and writing a tolerant parser that ignores unknown type values, but a stability contract from Anthropic would let us decouple from the pinned version and keep up with upstream improvements automatically.

Additional Context

References

Related issues we found while scoping this request (these illustrate the broader observation gap and are not duplicates of this request):

  • anthropics/claude-code#22968 -- high CPU/memory in long sessions; relevant because tools that observe CLI state often need to detect this condition.
  • anthropics/claude-code#34640 -- feature request for folder-scoped launches via URI handler.
  • anthropics/claude-code#36949 -- feature request for claudeCode.workingDirectory setting.
  • anthropics/claude-code#23119 -- JetBrains plugin discovery via the lock-file pattern (related observation surface).
  • anthropics/claude-code#24594 -- --input-format stream-json is undocumented; tool-builders depend on undocumented surfaces.
  • Datadog Security Labs CVE-2025-52882 -- IDE MCP bridge auth-token model.
  • Community dashboards depending on the JSONL: jspw/Claude-Code-Dashboard, nateherkai/token-dashboard, withLinda/claude-JSONL-browser, phuryn/claude-usage.
  • Statusline references that show how the JSON-on-stdin schema (already documented at code.claude.com/docs/en/statusline) became a community-stable contract -- a similar treatment of JSONL would be welcome.

Environment

  • Claude Code CLI: v2.1.118 / v2.1.119 (Linux + macOS spot-checks)
  • Claude Agent SDK (TypeScript): @anthropic-ai/claude-agent-sdk >= 0.2.86
  • VS Code 1.109+, Cursor 3.x, Windsurf
  • Operating systems: Linux (primary), macOS, Windows

Happy to provide further sample transcripts or specific spot-check evidence if useful.

View original on GitHub ↗

8 Comments

shafty023 · 3 months ago

I too would love to see this

csmarshall · 3 months ago

+1, same constraint from a different downstream. Building maury (multi-host claude code config sync with local-only transcript mining) — sessions aren't owned by maury; file-watching the JSONL is the only channel. Reader lives at src/maury/mining/transcripts.py.

Fields the reader consumes today: type, message.role, message.content[] (text and tool_use blocks), message.model, sessionId, cwd, timestamp. Everything else is opportunistic.

A versioned schema (e.g. schema_version on each record + a public types reference) would let downstream readers strict-validate and fail loud on drift. Today the failure mode is silent: a CC upgrade quietly changes a field, and the mining pass silently mis-parses transcripts before anyone notices. Strict-mode parsing + version pinning closes that hole.

vade-coo · 3 months ago

Hi all — small contribution to this thread. Over in vade-app/tjsonl we wrote up an observation-grounded v0.1 spec for the Claude Code transcript JSONL format and shipped a zero-dependency Python library + tj CLI that extracts and validates against it. It's grounded in an empirical 174-session sample plus the type enumeration from this issue (#53516), and the attachment.hook_success payload is fully specified.

Not asking Anthropic to adopt anything — just leaving a pointer in case it's useful for the folks here, and as an existence proof that the de-facto format is stable enough to spec from observation:

Both spec and tool are MIT-licensed. Empirical answer to the falsifier we held the spec to — does SessionStart fire on every session resume? — is yes (5 of 195 sessionIds in the v0 sample showed >1 distinct toolUseID for SessionStart).

Two RFC threads on the tracker are deliberately open for outside-expert input:

Peer-parser maintainers (ccusage, claude-code-log, simonw/claude-code-transcripts) — if you've been re-deriving this schema independently, the bigger win is if we converge. Happy to talk.

https://claude.ai/code/session_01PbL8tp361sjebPo78ov2AD

joshuataylor · 3 months ago

How will you handle versions of claude code? For example, earlier versions have different keys you're missing.

cruppertautomation-ai · 3 months ago

Cross-link for thread tracking: filed #61998 just now requesting that
the existing recap / post_turn_summary overlay also land as a JSONL
entry (proposed type: "recap"). Falls inside the lane this schema
issue already opened — different motivation (downstream consumption of
the CLI-generated summary), shared concern (a parseable, stable line
type). Flagging it here so anyone landing on this issue from a JSONL
schema angle sees both.

Necmttn · 2 months ago

A stable schema also needs an unknown-record policy.

For downstream watchers, the useful contract is: record type, schema version, session id, timestamp, parent/uuid fields where applicable, content-block shape, usage shape, and whether unknown top-level fields must be preserved. Add line-level source offsets and a fixture corpus, and third-party dashboards can be strict where it matters without breaking on new event types.

Generated with ax - https://github.com/Necmttn/ax

Odiph · 28 days ago

Second integrator hitting this, from a different direction, with concrete drift instances that may be useful evidence.

I maintain Claudictive, an Overwolf in-game overlay that lets developers talk to their existing Claude Code session without alt-tabbing out of a game. Like the original poster, we did not spawn the sessions we need to observe, so file-watching ~/.claude/projects/<encoded-cwd>/<uuid>.jsonl is our only option. We write via claude -p --resume <id>, but everything we render comes from parsing those transcripts.

The docs are explicit that this is unsupported: "The entry format is internal to Claude Code and changes between versions, so scripts that parse these files directly can break on any release." That is a fair warning, and it is exactly the problem. There is no supported alternative for reading a session you did not start, so the choice is parse-and-hope or do not build the integration.

Drift and undocumented behaviour we currently depend on

Verified against CLI 2.1.216 through 2.1.220 on Windows:

  1. Subagent tool name is Agent, not Task. The docs describe the subagent tool as Task; the value that actually appears in tool_use blocks is Agent. We found this empirically after our subagent handling silently stopped matching. This is the exact failure mode the warning predicts, and it is silent: nothing errors, features just quietly stop working.
  1. Project directory encoding is lossy and not reversible. The docs say non-alphanumeric characters are replaced with -. We decode -- back to / plus a drive-letter fixup, which is guesswork. We now prefer the cwd field from the first few lines of the transcript, but that field is itself undocumented.
  1. AskUserQuestion in a tool_use block is our only signal that a session is paused waiting for a human. Undocumented, empirically derived, and load-bearing: it drives whether our UI shows the user that their session needs them.
  1. Prompt on stdin under -p is behaviour we rely on and cannot find documented.
  1. Per-line fields we parse with no schema guarantee: message.role, message.content[] block shapes, type: 'user' | 'result', isSidechain, timestamp, cwd, and type: 'permission-mode' / type: 'mode' entries.

One concrete consequence, from this week

We read permission mode from the transcript so the overlay can inherit the posture the user set in their editor. Because we cannot know where in the file a permission-mode entry lives, and because reading whole multi-megabyte transcripts is not viable, we read a bounded trailing window. A user who set plan mode early in a long session has that entry fall outside the window, so we silently fail to inherit it. That is our bug to fix, but it exists because the file is an append-only log with no index and no documented way to ask "what is the current mode of this session".

What would actually help, in priority order

  1. A versioned schema field on each line, so a parser can detect drift and degrade loudly instead of silently rendering nothing.
  2. A documented subset, even a small one. We do not need every field frozen. role, text content, tool name, timestamp, cwd, and session state would cover most of what integrations do.
  3. A changelog entry whenever the format changes. Even without stability guarantees, knowing when to re-test is most of the value.
  4. Failing all of the above, a read-only query interface (claude session show <id> --json) would remove the need to parse files at all.

Happy to supply real transcripts or test against pre-release builds if that is useful.

kentjohnburchard · 18 days ago

A downstream mitigation, not a substitute for the requested contract: I maintain Traceplain, and the concrete drift cases in this thread prompted us to make unknown-shape handling fail loud.

The v1.2.0 GitHub Action now:

  • counts Claude top-level records and content blocks that it does not interpret;
  • changes the verdict to REVIEW NEEDED instead of silently omitting them;
  • reports counts only in safe mode, with bounded structural type names available only in names mode;
  • never includes the unknown record content in the generated handback.

The implementation and regression test are public in commit 114efc3, and v1.2.0 is green in hosted CI.

This catches the “feature quietly disappears” failure mode, but it cannot tell a benign new envelope from a consequential new session-state event. A per-line schema version, documented subset, and fixture corpus—or a read-only session JSON interface—are still the real fixes.