tl;dr

By the end of this article, you will be able to:

  • map Claude Code’s local bundle, OpenTelemetry stream, and Compliance API transcript to the questions each can answer;
  • retrieve centrally retained prompts, tool inputs, and tool results while preserving provenance and truncation;
  • join prompts, decisions, actions, and results with session.id, prompt.id, and tool_use_id;
  • preserve a local session bundle, including subagent records and spilled tool results;
  • configure metadata-first OTel and understand its sensitive-content gates;
  • build sequence detections and corroborate effects with host, Git, identity, network, or service evidence.

Find the recorded chain and reduce investigation time

Claude Code can execute commands, edit files, call MCP servers, run hooks, load plugins, and delegate work to subagents. Security teams need to reconstruct what the user asked, which control allowed an action, what the agent requested, what result returned, and whether an external system confirms the effect.

Anthropic documents evidence at three operational layers. A local plaintext bundle can contain messages, tool calls, tool results, subagent transcripts, large-output spill files, and file snapshots. Opt-in OpenTelemetry provides structured runtime events and explicit correlation keys. For eligible Enterprise organisations, the Compliance API exposes retained local-session metadata and ordered transcript messages with typed text, tool-use, and tool-result blocks.

No Claude Code binary, user configuration, or real transcript was inspected for this article. The local entry format is an internal implementation detail that can change, and several central fields require particular releases. Record the installed version before interpreting a field or deploying a rule.

Prompt and action content can include source code, credentials, messages, file paths, and tool output. Limit access to the people and systems that need it, and align retention with the detection or investigation purpose.

Map every source to a question

Source Start here when you need to know Boundary
Local session bundle What was recorded in one session, including subagent work and large results? Plaintext, locally mutable, version-variable, and subject to cleanup
Prompt history Which prompts were typed, when, and for which project? No assistant messages, tool calls, or outcomes
OpenTelemetry Which prompts, decisions, tools, API requests, MCP connections, hooks, plugins, and subagents should be searchable across a fleet? Opt-in, version-gated, and sensitive content is off by default
Compliance API Which retained prompts, tool requests, and results are available for a covered local session? Enterprise, authentication, provider, product, retention, and capture exclusions apply
Host and service records Did the command, file, Git, identity, network, or SaaS effect occur? Independent systems have their own identifiers and retention gaps
Evidence-source relationshipBring agent activity and independent effects into one investigation
  • Endpoint · mutableLocal bundle~/.claude/projects/...Session and subagent detail
  • Live · operator configuredOpenTelemetryclaude_code.*Decisions, actions, outcomes, policy
  • Central · eligible EnterpriseCompliance API/sessions/localRetained typed transcript messages
  • Independent · authoritative effectHost and servicesEDR · Git · IdP · DNS · SaaSCorroborate the outcome

The Analytics API sits outside this detection path. It returns daily user aggregates for adoption, activity, token use, and estimated cost. It does not provide the event-by-event prompt, decision, action, and result chain needed here.

Retrieve retained session content from the Compliance API

Anthropic exposes three explicit local-session resources:

GET /v1/compliance/apps/sessions/local
GET /v1/compliance/apps/sessions/local/{local_session_id}
GET /v1/compliance/apps/sessions/local/{local_session_id}/messages

The local-session API reference documents list and detail resources. The message endpoint returns ordered transcript messages. An investigator selects a retained session, reviews actor and time bounds, then pages through its messages.

Treat retrieval as three separate collection steps:

  1. List sessions for the investigation window and preserve every page cursor used.
  2. Retrieve the selected session metadata, including organisation, workspace, user, product surface, time bounds, and session truncation.
  3. Retrieve every message page with explicit tool-input and tool-result byte limits.
api_root="https://api.anthropic.com"
auth_header="x-api-key: ${ANTHROPIC_COMPLIANCE_ACCESS_KEY}"

curl --fail-with-body --silent --show-error \
  --request GET \
  --url "${api_root}/v1/compliance/apps/sessions/local" \
  --header "$auth_header" \
  --header "anthropic-version: 2023-06-01"

Select one returned ID and retrieve its metadata before opening message content:

session_id="local_session_EXAMPLE"

curl --fail-with-body --silent --show-error \
  --request GET \
  --url "${api_root}/v1/compliance/apps/sessions/local/${session_id}" \
  --header "$auth_header" \
  --header "anthropic-version: 2023-06-01"
curl --fail-with-body --silent --show-error \
  --request GET \
  --url "${api_root}/v1/compliance/apps/sessions/local/${session_id}/messages?tool_use_input_max_bytes=10000&tool_result_max_bytes=10000" \
  --header "$auth_header" \
  --header "anthropic-version: 2023-06-01"

The identifier and credential are placeholders. Compliance transcript access requires an eligible organisation, enabled access, a Compliance Access Key, and read:compliance_user_data. A Console Admin key reaches only the Activity Feed. Keep the key out of shell history and repositories, store responses in an access-controlled evidence location, and follow pagination cursors until collection is complete.

Read typed blocks, not a flattened chat string

Each message has an ID, role, creation time, model where applicable, provenance, and a content array.

Block Fields to retain What it establishes
text Text plus role, time, model, provenance Prompt or assistant text retained for that message
tool_use Tool name, tool-use ID, JSON-encoded input, truncation Recorded tool request and available arguments
tool_result Matching tool_use_id, tool name, text items, is_error, truncation What the client returned to Claude for that call

tool_use_id is the documented request-to-result join. Preserve encoded input as received. Truncated input may no longer be valid JSON, so record truncation before decoding. Non-text tool-result material is omitted. is_error describes the returned result, not every downstream effect.

The endpoint defaults tool_use_input_max_bytes and tool_result_max_bytes to 10,000 bytes each. A value of -1 requests the server maximum, approximately 1 MiB. Larger limits also increase the sensitive material that the collection path must protect. Record the requested limits with the response.

GET /v1/compliance/apps/sessions/local/{local_session_id}/messages?tool_use_input_max_bytes=10000&tool_result_max_bytes=10000

Extended thinking, the request system field, tool definitions, and raw non-text result content are not returned. A complete page set can still be an incomplete representation of the model request or external effect.

Reconstruct one Compliance transcript

The following synthetic, collector-normalised excerpt keeps the fields that matter to a detection. It is not a raw API response, and the prompts, paths, identifiers, and result are fictional.

{
  "session_id": "local_session_EXAMPLE",
  "message": {
    "id": "msg_user_01",
    "role": "user",
    "created_at": "2026-09-22T10:00:00Z",
    "provenance": "verified",
    "content": [
      {
        "type": "text",
        "text": "Review documentation in /workspace/example. Do not inspect files outside this project."
      }
    ]
  }
}
{
  "session_id": "local_session_EXAMPLE",
  "message": {
    "id": "msg_assistant_01",
    "role": "assistant",
    "created_at": "2026-09-22T10:00:03Z",
    "model": "example-model",
    "provenance": "verified",
    "content": [
      {
        "type": "tool_use",
        "id": "toolu_EXAMPLE",
        "name": "Bash",
        "input_json": "{\"command\":\"find /synthetic/home -name credentials\"}",
        "truncated": false
      }
    ]
  }
}
{
  "session_id": "local_session_EXAMPLE",
  "message": {
    "id": "msg_user_02",
    "role": "user",
    "created_at": "2026-09-22T10:00:04Z",
    "provenance": "verified",
    "content": [
      {
        "type": "tool_result",
        "tool_use_id": "toolu_EXAMPLE",
        "tool_name": "Bash",
        "is_error": false,
        "text": "/synthetic/home/.config/example/credentials",
        "truncated": false
      }
    ]
  }
}

The first block establishes an explicit project boundary. The second records an action outside that boundary. The third joins back through tool_use_id and records what the client returned to Claude. It does not establish that the returned path existed at acquisition time or that another process did not alter it. Endpoint file telemetry supplies that corroboration.

At ingestion, preserve the complete raw message and separately map session ID, message ID, role, creation time, model, provenance type and reason, block type, tool name, tool-use ID, is_error, and every truncation marker. Store the retrieval byte limits beside the record. This lets a rule distinguish an empty input from an input clipped by policy.

Preserve provenance and completeness markers

Compliance provenance distinguishes verified session content from client_asserted, synthetic_marker, and content_unavailable records. Unavailable content can cite elapsed retention, client abort, revoked encryption key, oversize content, or content that was not captured. not_captured remains a visibility gap and does not distinguish content that never arrived from content withheld by fail-closed storage policy.

Session metadata supplies organisation, workspace and stable user identifiers, product surface, retained time bounds, and a truncated flag. Email can be null after membership changes. One session is capped at 100,000 inference calls; a truncated session returns the earliest calls. Keep the stable user ID and truncation marker even when email is absent.

The Compliance retention guide states that local-session transcripts are retained for six years by default or the organisation’s configured finite conversation-retention period. This is separate from local cleanup. Zero-data-retention and HIPAA-ready organisations are excluded from capture.

The Compliance FAQ also excludes Claude Code cloud sessions, API-key-authenticated sessions, and sessions run through Amazon Bedrock, Google Cloud, or Microsoft Foundry. Capture occurs when covered requests reach the Claude API, so on-device activity that never reaches it is outside this source. Validate coverage before treating the API as mandatory capture.

Preserve the local bundle before cleanup removes context

Claude Code stores application data under ~/.claude by default. CLAUDE_CONFIG_DIR can relocate it. Record the release and inventory paths without printing content:

claude --version

find "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/projects" \
  -type f -name '*.jsonl' -print

For a selected session, preserve the parent transcript and inspect corresponding paths:

projects/<project>/<session>.jsonl
projects/<project>/<session>/subagents/
projects/<project>/<session>/tool-results/
file-history/<session>/
debug/

Anthropic’s application-data guide documents five related local sources. They do not share one public record schema:

Artifact What it contributes Relationship to the session Boundary
Parent transcript Conversation messages, tool calls, and tool results Top-level projects/<project>/<session>.jsonl JSON Lines framing is documented; the entry schema is internal and version-variable
Subagent transcripts Conversations delegated to subagents Nested under the same <session> directory Do not assume the same entry shape, fixed filenames, or a stable pointer from a parent record
Tool-result spill files Large tool outputs stored outside the parent transcript Nested under the same <session> directory No public contract defines the filename, media type, reference field, or one-to-one mapping
File-history snapshots Pre-edit content used for checkpoint restore Stored under file-history/<session>/ A snapshot shows prior content, not every edit, deletion, or final filesystem state
Debug logs Optional diagnostic context when debug logging was enabled Separate debug/ collection, attributable by session and time where possible Debug output is not a stable action ledger or an authoritative record of effects

No Claude Code binary or real local artifact was inspected for this article. The file roles above come from Anthropic’s documentation. The Codex article can go further inside local records because its examples came from an inspected, version-pinned transcript. Here, the contents remain mutable and their internal entry format remains undocumented. This evidence boundary is why this article does not show a pseudo-raw Claude transcript record.

Copy only the explicitly reviewed session artifacts into an access-controlled evidence directory and preserve their timestamps. The commands below operate on those copies. They establish integrity, inventory associated files, and test the selected parent copy’s JSON Lines framing without printing prompts, tool input, or results:

session_copy="/approved/evidence/claude/session-example.jsonl"
associated_copy="/approved/evidence/claude/session-example-associated"

shasum -a 256 "$session_copy"
wc -l "$session_copy"
jq -R 'fromjson | empty' "$session_copy" >/dev/null

jq -r 'type' "$session_copy" | sort | uniq -c
jq -c 'if type == "object" then (keys | sort) else [type] end' \
  "$session_copy" | sort | uniq -c

find "$associated_copy" -type f -exec wc -c {} +
find "$associated_copy" -type f -exec file --mime-type {} \;
find "$associated_copy" -type f -exec shasum -a 256 {} \;

jq -R 'fromjson | empty' reads each physical line as text and fails if that line is not one complete JSON value. It accepts valid values such as false and null, and does not validate a Claude-specific schema. Outer types and top-level key-set frequencies are discovery output for the recorded producer version, not a field contract. Inventory subagent files generically with the associated copies. Apply JSON parsing or key inspection to another file only after establishing its framing. Even filenames and key names can be sensitive, so store this inventory with the case. Stop at path, size, type, and hash for subagent files, spill files, snapshots, and debug logs until the investigation has a specific need and appropriate access controls for content review.

Anthropic documents release-gated correlation counterparts for selected runtime records. message.uuid and client_request_id require Claude Code 2.1.214 or later. api_response_body.message.uuid requires 2.1.274 or later. The same guide documents API request_id with internal transcript requestId, plus tool_use_id across tool decision and result events, without assigning those fields a minimum release. These names help test candidate joins against a preserved copy; they do not make the local representation stable. Preserve nulls, the producing version, and raw records because request identifiers can be absent and the transcript format can change on any release.

Directory and session association does not guarantee a stable record-to-file pointer. A successful tool lifecycle record does not prove the resulting filesystem, Git, identity, network, or service effect. Corroborate consequential outcomes with the authoritative external source.

Retention and disabled persistence create gaps

Anthropic’s application-data guide documents an age-based sweep for transcripts, subagent records, spilled results, snapshots, and debug logs. cleanupPeriodDays defaults to 30 days and has a one-day minimum. Releases from 2.1.248 treat sessions recently used through Claude Desktop or Cowork differently unless desktopSessionCleanupPeriodDays or managed cleanup applies. Version coverage matters.

Prompt history is kept until deletion and falls outside that sweep. CLAUDE_CODE_SKIP_PROMPT_HISTORY disables transcript and history persistence, while non-interactive -p sessions can use --no-session-persistence. Missing files indicate a collection gap, not an absence of activity. Avoid purge commands during collection.

Configure metadata-first OpenTelemetry

Claude Code OTel is disabled until enabled. Metrics and log exporters are separate. This example uses the reserved example.invalid domain and leaves sensitive content gates at defaults:

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_LOGS_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT="https://otel.example.invalid:4317"

claude

Replace the endpoint only with an organisation-controlled collector. Supply credentials through the organisation’s secrets mechanism.

Prompt text, assistant text, tool details, tool content, managed settings, and raw API bodies have separate gates: OTEL_LOG_USER_PROMPTS, OTEL_LOG_ASSISTANT_RESPONSES, OTEL_LOG_TOOL_DETAILS, OTEL_LOG_TOOL_CONTENT, OTEL_LOG_MANAGED_SETTINGS, and OTEL_LOG_RAW_API_BODIES. They are disabled by default. OTEL_LOG_TOOL_CONTENT requires tracing and remains subject to the configured content limit. Raw bodies can contain the entire conversation and tool material, and file output can be untruncated. Enable content for a defined detection need with matching access and retention controls.

Use event families as a detection contract

The monitoring guide documents per-prompt, API, tool decision, tool result, permission, authentication, MCP, plugin, skill, hook, compaction, and subagent events.

Question Starting event Fields and joins
Who and which client initiated activity? Resource attributes Organisation and available user identifiers, session.id, app version, entry point
Which prompt caused the chain? Prompt event prompt.id, timestamp, content only when enabled
Who allowed or blocked a tool? claude_code.tool_decision tool_use_id, decision, source, tool_source, tool name
Did the lifecycle finish? claude_code.tool_result tool_use_id, success, duration_ms, error_type, input/result sizes
Was a new MCP server involved? MCP connection plus tool events Session, prompt, scope; detailed input requires its gate
Did a hook intervene? Hook events Event, name, source, safe mode, blocking count
Did code reach version control? Tool result plus repository attributes Head revision and branch, then authoritative Git evidence

Several detailed tool fields require 2.1.214 or later. Managed selector behaviour requires 2.1.223 or later, and related launcher handling is documented for 2.1.251 or later. Validate the fleet before treating a field as universal.

tool_decision.source can indicate configuration, a hook, or a temporary or persistent user choice. source=config combines distinct policy paths, including some callback failures, so it does not identify the exact matching rule.

The action pair has asymmetric semantics. A rejected request appears in a tool-decision event and has no corresponding tool-result event because the tool did not run. An accepted request can produce both a decision and a later result with the same tool_use_id. The result can add success, duration, error class, decision source, and input or result sizes. Optional input and parameter detail remains subject to content gates and truncation.

For detection-ready storage, keep these layers distinct:

Layer Fields worth retaining Why it matters
Raw OTel Original resource, scope, log body, and attributes Supports reprocessing when mappings or versions change
Runtime identity Organisation and user attributes, app version, entry point, terminal type Distinguishes who ran which client and whether a rule applies
Correlation session.id, prompt.id, tool_use_id, request and message identifiers Reconstructs prompt-to-action chains without relying only on time
Control Permission mode, decision, source, tool_source, hook fields Shows how the action crossed a control boundary
Outcome success, duration, error type, input and result sizes Separates denied, failed, and completed lifecycle states
Collection state Content gates, collector receipt time, exporter and protocol Explains why content may be absent and when the event became searchable

Reconstruct prompt to effect as a sequence

After collecting the relevant Compliance, local, and OTel records, use this optional synthesis step only when the detection question needs a cross-source chain. Correlate task scope, control decision, requested action, returned result, and an independent effect. One tool name rarely proves intent.

Follow the recorded chain, then verify the effect. Identifiers preserve causality without treating an agent result as a host audit record.
  1. 1PromptObjective and boundarysession.id · prompt.id
  2. 2DecisionAccept, reject, or persisttool_use_id · source
  3. 3ActionTool name and inputtool_use_id
  4. 4ResultSuccess, error, content, sizetool_use_id
  5. 5CorroborateHost, Git, identity, network, serviceindependent record

session.id scopes the run. prompt.id groups activity caused by one prompt in OTel. tool_use_id joins decision, request, and result where the source exposes those families. Sort OTel by event.timestamp, then use event.sequence only to break ties within one process. It can repeat or decrease when a session resumes.

The event-correlation guide states that message.uuid and client_request_id require Claude Code 2.1.214 or later, while request_body_id and api_response_body.message.uuid require 2.1.274 or later. It also documents request_id joins without assigning that field a minimum release. Keep joins in a version-aware adapter. Missing identifiers remain null, and a narrow time match remains a candidate relationship.

A synthetic credential-access hunt could read:

  1. A prompt limits the task to documentation in one repository.
  2. claude_code.tool_decision records an accepted Bash request, including decision source and tool provenance where enabled.
  3. Approved detailed telemetry, the local transcript, or a Compliance tool_use block shows input targeting a credential path outside the project.
  4. claude_code.tool_result or a Compliance tool_result reports success and returned content, with truncation state.
  5. Endpoint telemetry confirms the read. Network or destination-service records confirm any later transfer.

The sequence deserves investigation because the action crosses the prompt and project boundary. Migration, incident response, and authorised administration can look similar. The detection should assemble evidence for review, not assign intent. The reconstructed chain can then drive the boundary-crossing detections below.

Build detections from control-boundary crossings

Permission broadening followed by a sensitive call

Look for broader permission mode followed by an accepted Bash or MCP decision in the same session and prompt. Raise confidence when approved content telemetry, Compliance, or local records show credential paths, persistence locations, or transfer. Confirm with endpoint and network evidence.

An unfamiliar MCP connection followed by use

Join a new MCP connection to later decisions and results. Compare scope and provenance with the approved inventory. A connection alone is weak evidence. Accepted sensitive calls and an unexpected destination strengthen the chain.

A policy block followed by another successful route

Correlate a blocking hook with a later related success in the same session. Review effective policy and action details. Policy updates, false positives, and legitimate retries can resemble bypass.

Plugin installation, load, and subsequent activity

Join plugin installation to load, MCP activity, and a sensitive decision. Preserve plugin source and version, session and prompt IDs, decision source, and destination evidence.

Persistent approval followed by repeated calls

Join a user_permanent decision to later config-approved calls. The persistent choice is context. Escalate when later calls cross the original project or data boundary.

Compare Claude Code and Codex like for like

Security question Claude Code OpenAI Codex Practical conclusion
Central prompts and responses? Compliance typed text blocks with role, time, model, provenance, truncation CODEX_LOG prompt and response events with text and correlation fields Both support content-aware detections for eligible deployments
Tool arguments? tool_use.input with truncation; OTel detail separately gated TOOL_CALL_* serialized tool_input; app MCP arguments Preserve input plus truncation or content-policy state
Tool results? tool_result text, error, and truncation joined by tool_use_id General tool events report status; app MCP can include truncated preview Claude documents richer general Compliance result content; neither proves effect
Approval decisions? OTel decision, source, provenance, and tool_use_id Cloud Agent TOOL_DECISION; Codex OTel runtime decisions Validate client and version coverage
Central retrieval shape? Paginated sessions and ordered messages Immutable roughly ten-minute JSONL files Use source-specific collectors and validated normalisation
Strongest local source? Documented parent/subagent bundle, spills, snapshots, debug Session JSONL, history, app logs with version-pinned observations Both are mutable; Claude’s file map is more documented

No public OpenAI endpoint equivalent to Anthropic’s session list, detail, and messages trio was documented in the reviewed sources. OpenAI’s event-file alternative still exposes prompt, response, tool input, decision, client, and lifecycle events through CODEX_LOG. API shape should not be mistaken for telemetry richness.

Codex commonly pivots on session_id, then turn_id, call_id, or tool_call_id. Claude OTel pivots on session.id, prompt.id, and tool_use_id; Compliance adds message ID, provenance, and truncation. Preserve raw records and keep missing joins missing.

What these records can and cannot tell you

Evidence source Claude Code activity records Useful recorded context. External effects require independent evidence.

Can answer What the available records captured

  • Which retained Enterprise user, organisation, workspace, product surface, and session surrounded a Compliance message?
  • Which prompt, assistant text, tool request, and returned text result were retained, including provenance and truncation?
  • Which OTel decision allowed or rejected a tool, where it came from, and whether an accepted invocation later reported success or failure?
  • Which session, prompt, and tool invocation connect through session.id, prompt.id, and tool_use_id where the sources supply them?
  • Which local parent or subagent transcript, spilled result, pre-edit snapshot, or optional debug record adds endpoint context?
  • Which content gates, version requirements, retention markers, and missing-value states constrain the evidence?

Cannot prove What happened outside the captured record

  • Every session or action was captured across authentication, provider, cloud-session, OTel, persistence, cleanup, and retention boundaries.
  • A tool request, success value, or returned text caused a filesystem, Git, identity, network, or SaaS effect.
  • Missing content or an absent event means the action did not occur. Redaction, truncation, disabled persistence, and collection gaps can create absence.
  • All model context is present. Compliance omits extended thinking, tool definitions, request system content, and raw non-text results.
  • Local plaintext is immutable or complete, or that its internal entry structure remains stable across releases.
  • A boundary-crossing sequence establishes malicious intent. It remains a triage signal that needs task context and corroboration.

Publish the evidence model and corroborate effects

We publish the evidence model, exact routes, join keys, content gates, retention boundaries, and negative findings so another team can reproduce and challenge the work. The same research shapes our product design: retain raw source records, preserve provenance and content-policy state, build versioned adapters, and detect sequences across agent and independent system evidence.

For a consequential finding, preserve the raw source and acquisition metadata, keep version and correlation identifiers, record gates and truncation, and corroborate the effect in the authoritative host or service. That chain supports rich detections while keeping conclusions proportional to the evidence.