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>.jsonlwith a sidecaragent-<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’sagentId: subagent records carry the parent’ssessionId, which is kept innative. -
Entity keys:
messageIdis the recorduuidfor user and harness records, and the APImessage.idfor an assistant response.callIdistool_use.id; results link bytool_result.tool_use_id.inferenceIdis the assistantmessage.id, which is the provider’s response ID and is on every assistant line. The HTTPrequestIdis on only about a fifth of assistant lines and is not the inference ID.turnIdis the user record’spromptId(also the OpenTelemetryprompt.id). -
Who wrote a message: read
origin.kind,promptSource,turnOrigin,isMetaand the text markers, not the record type:origin.kind: human→author: human. This includes aqueued_commandattachment whose origin kind ishuman: 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 istask_completion, orschedulewhen the subkind isscheduled-trigger.origin.kind: peer,coordinatororobserver→author: agent; a peer message isorigin: inter_sessionwithfromnamingfromSessionas a session, orfromandnameas 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
toolUseIdand the parent result’sagentId→author: agent; being the first record in the transcript does not by itself identify the sender. origin.kind: unclassified, or no marker →authorabsent.
-
Triggers:
turnOrigingoes intrigger.native.kindmapping:scheduled→schedule,task_notification→task_completion,auto_continuation→auto_continuation,peer→peer.humanandsdkturns get no trigger.scheduledTaskId→trigger.id,scheduledFireId→trigger.runId,fireReason→trigger.reason. -
Repeated metadata:
ai-title,custom-title,last-prompt,mode,permission-mode,pr-linkand other metadata lines are written again at each exit or turn. A repeated identical value is not a change: writesession_infoorconfig_changeonly when the value changes.ai-title→titleSource: model;custom-title→titleSource: user. -
Tools: model
tool_useblocks →requestedBy: model;!shell commands →requestedBy: user.kindby 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.attributionMcpServerandattributionMcpToolremain on the inference atattributedTo; 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_alwayswhensourceisuser_permanent),reject→reject_once;decidedByfromsource:config→policy,user_temporary,user_permanent,user_reject,user_abort→user,hook→hook;reasonType: classifier→decidedBy: classifier.toolDenialKindsets the result’sstatus. Claude Code stores no request title or options for these decisions, so they are compact decisions, notpermissionrecords (ASR-279). -
Questions:
AskUserQuestion→input_requestwithmode: questionsandcallId. Questions have no IDs:idis the question’s 0-based position as a string. The result’sanswersmap (keyed by question text, with several choices joined by commas) →answerswithselectedwhen the text equals an option label andtextotherwise; the free-textresponse→response; a result resolved byafkTimeoutMs→status: timed_out. -
Background work: a background Bash command (
backgroundTaskId) →terminalwithkind: shell;Monitor→terminalwithkind: monitor;TaskStop→status: stopped. A background agent (Agentwithrun_in_background) →subagentwithasync: true, revised fromtask_statusattachments (taskType: local_agent) and task notifications;local_bashtask status revises the terminal. -
Hooks:
hook_success,hook_non_blocking_error,hook_blocking_error,hook_cancelled,hook_error_during_executionandasync_hook_responseattachments →hook(stdout,stderr,exitCode,command,durationMs;timedOutfromhook_cancelled.timedOut;status: blockedfor a blocking error).stop_hook_summary→ onehookperhookInfosentry withevent: stop.hook_additional_context→ amessagewithauthor: hook. -
Plans:
ExitPlanModeinputplan→planwithplanType: document,planIdfrom the plan file name,sourcethe plan file.TodoWrite→planType: items. Task tools →planType: itemswithentryId,ownerandblockedBy. -
Request context: each
api-requestrecord describes the request of the call whosemessage.iditsrequestRefmaps to:base(the earlier request) →prefixFrom.inferenceId(themessage.idof the base request’s response),keep→prefixFrom.count,tail→entriesas blobs holding the messages fromapi-request-blob,params→inference.params. The unit is one message.requestDigestgoes inrequestDigestonly when it is a SHA-256 of the request bytes. -
Memory: instruction files in the
instructionsattachment 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/, flaggedmemdirStampedon the result) or amemory_saved {writtenPaths}event →memory_change {store: "auto_memory", scope: "project", op: add | update, target.path, cause.callId}, withstatus: appliedwhen the write’s result succeeded. Writes under~/.claude/agent-memory/<agentType>/→store: "agent_memory",scope: "agent". -
Workspace and VCS:
gitOperationon a Bash result →vcswithevidence: parsed_output(Claude Code parsed the command output); itspr.actionvaluescreated,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→vcspr_linkwithevidence: reported.edited_text_file→file_changewithcause: {actor: external, observed: true}.bashEditDiff→file_change: keep a booleanmoreFilesas recorded; for a nonnegative integer count, map 0 tofalseand a positive count totrue, retaining the count atcom.anthropic.claude-code/moreFilesCount. For negative or nonintegral values, retain the native value and omit the normalized boolean;userModified→modifiedByUser.environment.changesofworkingDirectory→workspace_state.cwd; added working directories →declaration.roots;isWorktree→workspace_state.worktree.linked.file-history-snapshot→checkpointwithkind: snapshotandfilesobjects{path, version, blob}. -
Configuration:
permission-modeentries andpermissionModeon prompts →config_change.permissionMode(on change only); themodeentry →config_change.mode;entrypoint→ headerclient.name.deferred_tools_delta→instructionswithkind: tool_definitionsandremoved; its failed and needs-auth MCP servers →config_change.capabilitywithop: failorneeds_auth. -
Notes: a
compact_boundaryand the following user record flaggedisCompactSummaryform onecompaction(the summary is not a message, ASR-373);compactMetadata.durationMs→durationMs. Server-side context edits (context_management.applied_edits) report counts only →compactionwithkind: pruneandreplaces: {state: unknown}.cache_creation.ephemeral_5m_input_tokensandephemeral_1h_input_tokens→cacheWriteByTtl5mand1h; keep both the split values and the aggregate even when they disagree (ASR-326);service_tier→params.serviceTier.cost-statetotals map to ausage_summarywithscope: session,cumulative: trueandincludesChildren: null. Aqueue-operationwithoperation: remove→ the queued message withstatus: withdrawn. Attachments that store structured data (IDE selections, environment snapshots) →dataparts withschema: "com.anthropic.claude-code/attachment/<type>", with a capture state describing the structured content actually held (ASR-238). UI-only attachments stay indisplayContent; 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 (capturestateexternalortruncated). 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 asdeleted_by_retention.
Position basis
- Folding a response: First, keep the latest observation of each repeated
uuidat the position where that UUID first appeared. Then fold distinct response blocks sharingmessage.idin first-seen order (ASR-511), withassembly.mode: folded. Take usage and stop reason from the latest native observation. Declarerevision_historyfor overwritten UUID snapshots, but not for ordinary growth. For an append, later source positions use the enrichment chain in ASR-511; source positions areline:<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
Links
-
Lineage: a subagent has
spawnedByfrom the parent’s tool call that launched it (the sidecar’stoolUseId, and the parent’s tool result naming theagentId), withevidence: native_field,scope: subagentandrolefromagentType. Records copied into a new session by fork or branch carryforkedFrom {sessionId, messageUuid}: map them tocopiedFromwithproof: native_fieldand the session tolineage.forkedFrom. Afork-context-ref {parentSessionId, parentLastUuid}record means the parent’s prefix is referenced, not copied:inherits.mode: referenced. Acontinued-in {continuedInSessionId}record →session_info.continuedIn.rootis not claimed from a transcript alone: a fork whose copied records were compacted away, or a session that another session’scontinued-innames, looks like a root in its own file. -
Tree: native parent
parentUuid; at asystemrecord with subtypecompact_boundary,parentUuidis null and the logical parent islogicalParentUuid. Response key:message.id. Active leaf: the latestlast-promptrecord’sleafUuid. Injected: as under “Who wrote a message”. Failed attempts: synthetic assistant records flagged as API errors (isApiErrorMessage).
Claude Code link contracts
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.