claude-code

Identity

  • Native format: claude-code-jsonl. One JSONL file per session under ~/.claude/projects/<encoded working directory>/<sessionId>.jsonl. Subagent transcripts in <sessionId>/subagents/agent-<agentId>.jsonl with a sidecar agent-<agentId>.meta.json. The project directory name is a lossy encoding of the working directory and does not establish identity.
  • Session ID: for a main transcript, the records’ sessionId (equal to the file name). For a subagent transcript, the subagent’s agentId: subagent records carry the parent’s sessionId, which is kept in native.
  • Entity keys: messageId is the record uuid for user and harness records, and the API message.id for an assistant response. callId is tool_use.id; results link by tool_result.tool_use_id. inferenceId is the assistant message.id, which is the provider’s response ID and is on every assistant line. The HTTP requestId is on only about a fifth of assistant lines and is not the inference ID. turnId is the user record’s promptId (also the OpenTelemetry prompt.id).
  • Who wrote a message: read origin.kind, promptSource, turnOrigin, isMeta and the text markers, not the record type:
    • origin.kind: human → author: human. This includes a queued_command attachment whose origin kind is human: that is a person steering mid-turn (origin: steer), not harness context.
    • isMeta: true, and attachments other than human queued commands → author: harness, origin: injected.
    • origin.kind: task-notification → author: harness, origin: injected; the turn’s trigger is task_completion, or schedule when the subkind is scheduled-trigger.
    • origin.kind: peer, coordinator or observer → author: agent; a peer message is origin: inter_session with from naming fromSession as a session, or from and name as an address.
    • origin.kind: channel (a message from an MCP channel server) → via: channel.
    • <command-name> markers → origin: command; <local-command-stdout> → author: harness.
    • A subagent launch prompt identified by the sidecar toolUseId and the parent result’s agentId → author: agent; being the first record in the transcript does not by itself identify the sender.
    • origin.kind: unclassified, or no marker → author absent.
  • Triggers: turnOrigin goes in trigger.native. kind mapping: scheduled → schedule, task_notification → task_completion, auto_continuation → auto_continuation, peer → peer. human and sdk turns get no trigger. scheduledTaskId → trigger.id, scheduledFireId → trigger.runId, fireReason → trigger.reason.
  • Repeated metadata: ai-title, custom-title, last-prompt, mode, permission-mode, pr-link and other metadata lines are written again at each exit or turn. A repeated identical value is not a change: write session_info or config_change only when the value changes. ai-title → titleSource: model; custom-title → titleSource: user.
  • Tools: model tool_use blocks → requestedBy: model; ! shell commands → requestedBy: user. kind by name: Bash, PowerShell → execute; Edit, Write, NotebookEdit → edit; Read → read; Glob, Grep, ToolSearch → search; WebFetch, WebSearch → fetch; everything else → other. MCP tool names are composites (mcp__<server>__<tool>): provider {kind: mcp, identity: unknown}, without a native call association. attributionMcpServer and attributionMcpTool remain on the inference at attributedTo; their position after a result does not identify a call.
  • Approvals: permissionDecision {decision, source, reasonType} on a tool result → tool_result.permission: accept → allow_once (allow_always when source is user_permanent), reject → reject_once; decidedBy from source: config → policy, user_temporary, user_permanent, user_reject, user_abort → user, hook → hook; reasonType: classifier → decidedBy: classifier. toolDenialKind sets the result’s status. Claude Code stores no request title or options for these decisions, so they are compact decisions, not permission records (ASR-279).
  • Questions: AskUserQuestion → input_request with mode: questions and callId. Questions have no IDs: id is the question’s 0-based position as a string. The result’s answers map (keyed by question text, with several choices joined by commas) → answers with selected when the text equals an option label and text otherwise; the free-text response → response; a result resolved by afkTimeoutMs → status: timed_out.
  • Background work: a background Bash command (backgroundTaskId) → terminal with kind: shell; Monitor → terminal with kind: monitor; TaskStop → status: stopped. A background agent (Agent with run_in_background) → subagent with async: true, revised from task_status attachments (taskType: local_agent) and task notifications; local_bash task status revises the terminal.
  • Hooks: hook_success, hook_non_blocking_error, hook_blocking_error, hook_cancelled, hook_error_during_execution and async_hook_response attachments → hook (stdout, stderr, exitCode, command, durationMs; timedOut from hook_cancelled.timedOut; status: blocked for a blocking error). stop_hook_summary → one hook per hookInfos entry with event: stop. hook_additional_context → a message with author: hook.
  • Plans: ExitPlanMode input plan → plan with planType: document, planId from the plan file name, source the plan file. TodoWrite → planType: items. Task tools → planType: items with entryId, owner and blockedBy.
  • Request context: each api-request record describes the request of the call whose message.id its requestRef maps to: base (the earlier request) → prefixFrom.inferenceId (the message.id of the base request’s response), keep → prefixFrom.count, tail → entries as blobs holding the messages from api-request-blob, params → inference.params. The unit is one message. requestDigest goes in requestDigest only when it is a SHA-256 of the request bytes.
  • Memory: instruction files in the instructions attachment map by type: User → user, Project → project, Local → local, Managed → managed, AutoMem → memory. A write to the project memory directory (~/.claude/projects/<encoded working directory>/memory/, flagged memdirStamped on the result) or a memory_saved {writtenPaths} event → memory_change {store: "auto_memory", scope: "project", op: add | update, target.path, cause.callId}, with status: applied when the write’s result succeeded. Writes under ~/.claude/agent-memory/<agentType>/ → store: "agent_memory", scope: "agent".
  • Workspace and VCS: gitOperation on a Bash result → vcs with evidence: parsed_output (Claude Code parsed the command output); its pr.action values created, edited, merged, commented, closed, reopened, ready, draft → pr_open, pr_update, pr_merge, pr_comment, pr_close, pr_reopen, pr_ready, pr_draft. pr-link → vcs pr_link with evidence: reported. edited_text_file → file_change with cause: {actor: external, observed: true}. bashEditDiff → file_change: keep a boolean moreFiles as recorded; for a nonnegative integer count, map 0 to false and a positive count to true, retaining the count at com.anthropic.claude-code/moreFilesCount. For negative or nonintegral values, retain the native value and omit the normalized boolean; userModified → modifiedByUser. environment.changes of workingDirectory → workspace_state.cwd; added working directories → declaration.roots; isWorktree → workspace_state.worktree.linked. file-history-snapshot → checkpoint with kind: snapshot and files objects {path, version, blob}.
  • Configuration: permission-mode entries and permissionMode on prompts → config_change.permissionMode (on change only); the mode entry → config_change.mode; entrypoint → header client.name. deferred_tools_delta → instructions with kind: tool_definitions and removed; its failed and needs-auth MCP servers → config_change.capability with op: fail or needs_auth.
  • Notes: a compact_boundary and the following user record flagged isCompactSummary form one compaction (the summary is not a message, ASR-373); compactMetadata.durationMs → durationMs. Server-side context edits (context_management.applied_edits) report counts only → compaction with kind: prune and replaces: {state: unknown}. cache_creation.ephemeral_5m_input_tokens and ephemeral_1h_input_tokens → cacheWriteByTtl 5m and 1h; keep both the split values and the aggregate even when they disagree (ASR-326); service_tier → params.serviceTier. cost-state totals map to a usage_summary with scope: session, cumulative: true and includesChildren: null. A queue-operation with operation: remove → the queued message with status: withdrawn. Attachments that store structured data (IDE selections, environment snapshots) → data parts with schema: "com.anthropic.claude-code/attachment/<type>", with a capture state describing the structured content actually held (ASR-238). UI-only attachments stay in displayContent; do not use the unavailability of rendered text as the capture state for nonempty structured content. No numeric exit code is recorded for shell commands. Large tool outputs are stored in separate files that may be deleted under the retention policy (capture state external or truncated). File order and timestamp order often disagree; order by file position (ASR-014). Transcripts are deleted after 30 days by default, which bundle manifests record as deleted_by_retention.

Position basis

  • Folding a response: First, keep the latest observation of each repeated uuid at the position where that UUID first appeared. Then fold distinct response blocks sharing message.id in first-seen order (ASR-511), with assembly.mode: folded. Take usage and stop reason from the latest native observation. Declare revision_history for overwritten UUID snapshots, but not for ordinary growth. For an append, later source positions use the enrichment chain in ASR-511; source positions are line:<n> within the stable transcript, or native IDs when unique.
Use only positions established by the stated format and mapping evidence. Current-state rows without transcript positions form the snapshot tail. A protocol commit boundary takes precedence over physical EOF; uncommitted bytes are not a gap.

Destinations

Claude Code: native facts without a standard field

  • Lineage: a subagent has spawnedBy from the parent’s tool call that launched it (the sidecar’s toolUseId, and the parent’s tool result naming the agentId), with evidence: native_field, scope: subagent and role from agentType. Records copied into a new session by fork or branch carry forkedFrom {sessionId, messageUuid}: map them to copiedFrom with proof: native_field and the session to lineage.forkedFrom. A fork-context-ref {parentSessionId, parentLastUuid} record means the parent’s prefix is referenced, not copied: inherits.mode: referenced. A continued-in {continuedInSessionId} record → session_info.continuedIn. root is not claimed from a transcript alone: a fork whose copied records were compacted away, or a session that another session’s continued-in names, looks like a root in its own file.
  • Tree: native parent parentUuid; at a system record with subtype compact_boundary, parentUuid is null and the logical parent is logicalParentUuid. Response key: message.id. Active leaf: the latest last-prompt record’s leafUuid. Injected: as under “Who wrote a message”. Failed attempts: synthetic assistant records flagged as API errors (isApiErrorMessage).
These contracts apply to the observed 2.1.219 to 2.1.305 formats above. A converter tests the fields it uses for each claimed version. For claude-code-hooks 2.1.305, session_id identifies the containing session, not the session that spawned it. Stop, SubagentStop and PreCompact callbacks precede final outcomes. Permission callbacks vary by launch mode, and the working directory can change without a CwdChanged callback. A pending status means the outcome is unresolved in this capture. Covering an event is not the same as covering every field in its payload; publish a policy saying which source fields are covered. Positions assigned when hooks are appended apply only within that capture; native prompt and tool IDs remain unchanged.

Usage

Select one report per native accounting identity only when this entry establishes that identity. Keep unjoinable or overlapping reports as native evidence. Declare incomplete semantic coverage. Schema defaults and placeholder zeros are not reported usage. Backend inclusion rules require evidence; do not infer them from a model name.

Lane and unassigned facts

Facts without established session ownership use manifest _meta["com.anthropic.claude-code/unassigned"] with the common shape. These facts may be retained with any selection of sessions, including no sessions, but the selection must not imply session ownership. State the selection policy in the export report.

Losses

Declare every applicable loss under ASR-157. Missing model rendering, accounting joins, lineage or exact context boundaries remain losses when this entry does not qualify them. Unknown ownership is not itself lost data. When unassigned facts are omitted, record that in a documented vendor loss or report, not in a new core category.

Evidence

Basis: observed (closed source; local transcripts of versions 2.1.219 to 2.1.305, and the installed 2.1.305 binary, which embeds its own schemas). Evidence applies only to the named formats and explicit mappings above. A real capture covers only exercised behavior. A writer probe that makes no model calls covers persistence, not model or tool execution. Authored tests do not qualify an agent version. Unlisted formats and link mappings remain unqualified.