Part of the AI coding agent security checklist
A Claude Code audit log from what is already on disk
Before you install a logging hook, look at what Claude Code already wrote down: every tool call from the last 30 days, by default.
Checked against Claude Code's documentation, the advisories and research under Sources, and ranwhat's source. Published .
The record you already have
Claude Code saves each session to a transcript as you work. Its documentation describes the file as the full conversation: every message, every tool call and every tool result. That is an audit log in all but name, and it is already on your disk.
To keep more than a month, raise the setting in ~/.claude/settings.json. The minimum is 1, and 0 fails validation rather than meaning forever. A higher value keeps what the sweep has not reached yet. It does not bring back what it already deleted.
{
"cleanupPeriodDays": 365
}Look without installing anything
ls -lt ~/.claude/projects/*/*.jsonl | head # newest sessions first grep -lF -- 'git push --force' ~/.claude/projects/*/*.jsonl claude --resume <session-id> # then /export to save it as text
The file name is the session ID. grep tells you which transcripts contain a string, not that the command ran: the same text turns up in a file the agent read, in a search pattern or in an echo. /export writes the conversation as plain text with tool output rendered, which reads better than raw JSON. Claude Code's documentation says the line format is internal and changes between versions, so any script that parses it can break on a release, ranwhat included.
Read it back with ranwhat watch
A month of transcripts is too long to read and mostly routine. ranwhat watch reads them where they are and reports the calls worth a second look. No hook, no proxy, and it sends nothing anywhere.
uvx runs it without installing it. --days 90 finds more if cleanupPeriodDays was already above 30, if a session you resumed within the retention period has older actions in it, or for sessions started or last continued in Claude Desktop or Cowork, which Claude Code v2.1.248 and later keep at any age by default.
Nine rules
| Rule | Severity | For example |
|---|---|---|
| Credential access | Critical | cat ~/.aws/credentials, Read on .env |
| Secret-shaped string in a tool call | Critical | sk_live_… ghp_… AKIA… |
| Package or release published | Critical | npm publish twine upload docker push |
| Cloud resource destroyed or modified | Critical | terraform destroy kubectl delete aws iam attach-… |
| Financial API called | Critical | Stripe charges, refunds, transfers or payouts |
| Log or history tampering | Critical | history -c aws cloudtrail stop-logging |
| Destructive git | High | git push --force git reset --hard |
| Recursive deletion | High | rm -rf ~/Documents/old-notes find . -delete |
| Local file sent to the network | High | curl -d @dump.sql curl -F [email protected] |
Severity follows the target, not the verb: rm -rf build is not reported, rm -rf ~/Documents is high and rm -rf "$HOME" is critical.
As a log you can keep
ranwhat watch --json prints one record per flagged call:
{
"source": "claude-code",
"session": "3f2a9c1e-0b7d-4e55-9a10-2c6e8d4b7f01",
"project": "-Users-you-app",
"timestamp": "2026-09-26T14:42:10.000Z",
"tool_name": "Bash",
"tool_call_id": "toolu_01",
"payload_hash": "9006c7010e00d3a2",
"severity": "high",
"hits": [
{
"rule": "fs.destructive",
"severity": "high",
"title": "Bulk or recursive deletion",
"why": "Recursive deletion. Recoverable only if something else was backing it up.",
"evidence": "rm -rf ~/Documents/old-notes"
}
]
}ranwhat watch --json | jq -r '.[] | [.timestamp, .severity, .tool_name, .hits[0].evidence] | @tsv'
Log every command from now on with a hook
The transcript keeps everything for a month. For one line per command, kept as long as you like, Claude Code's hooks guide has a ready example.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
}
]
}
]
}
}In ~/.claude/settings.json it covers every project on this machine. In a repository's .claude/settings.json it covers that project and can be committed. If the file already has a hooks key, merge into it. The command needs jq on your path, and /hooks lists what is registered.
The hook's input also carries session_id, cwd and hook_event_name. For one JSON line per command with a time on it, use this as the command instead:
jq -c '{time: (now | todate), event: .hook_event_name, session: .session_id, cwd: .cwd, command: .tool_input.command}' >> ~/.claude/command-log.jsonlSend it to your own collector
A hook writes to this machine. OpenTelemetry sends events to a collector you run, as they happen. Claude Code's monitoring documentation calls these events the audit data source for Claude Code activity.
export CLAUDE_CODE_ENABLE_TELEMETRY=1 export OTEL_LOGS_EXPORTER=otlp export OTEL_EXPORTER_OTLP_PROTOCOL=grpc export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 export OTEL_LOG_TOOL_DETAILS=1 claude
Which one to use
| Record | Covers | Lives | Watch for |
|---|---|---|---|
| Transcript | Already there: the last 30 days, or cleanupPeriodDays | Local plaintext, editable by anything running as you | Silent deletion after the period; a format that changes between versions |
| Hook | From the moment you add it, for as long as you keep the file | A local file you define | Failed commands unless you add PostToolUseFailure; disableAllHooks in a project |
| OpenTelemetry | From the moment you enable it, for as long as your backend keeps it | Off the machine, at your collector | Only what the gates allow: no commands without OTEL_LOG_TOOL_DETAILS=1 |
Most people want the first today and one of the other two going forward. Run uvx ranwhat watch --days 30 over what is on disk now. Then add the hook if the record can stay on this machine, or OpenTelemetry if it has to leave it, where a later edit here cannot reach it.
A policy is not a record
Settings say what should happen. The transcript says what did. Before 2.1.260, a Claude Code session signed in to a Team or Enterprise account fetched the organisation's server-managed settings with an API key it had stored earlier, from a past /login or written into its config. When the settings endpoint rejected that key, the session still ran as the organisation's account, but with none of its server-managed policy (no deny rules, model restrictions or managed-only locks), or with a stale cached copy (CVE-2026-103012, published 29 September 2026). Enterprise was affected from 2.0.68 and Team from 2.1.38. MDM and file-based managed settings were not.
claude --version # 2.1.260 or later /permissions # the organisation's rules should be listed uvx ranwhat watch --days 30 # what ran while they may not have applied
watch reads only what retention kept, and a session nobody used within cleanupPeriodDays is already gone. Administrators can set requiredMinimumVersion to 2.1.260 through MDM or a managed settings file.
Nor does the transcript record a change to the configuration itself. Mitiga showed an npm postinstall script marking common clone paths as trusted in ~/.claude.json, after which a hook in a repository cloned there re-pointed an OAuth MCP server at a proxy on localhost at every start, and read each token that passed. Anthropic ruled it out of scope, Mitiga reports, because the chain depends on consent the user has already given. Mitiga's advice is to watch ~/.claude.json and a project's MCP files for changes, and to keep a list of the MCP endpoints you approved. Hooks deserve the same watch: /hooks lists what is registered.
When the log shows a deletion
An rm -rf in the record tells you what ran. Whether the files can come back is a separate question, and checkpoints will not answer it: Claude Code's checkpointing does not track files changed by Bash commands. Start with the guide to files Claude Code deleted. For everything else Claude Code keeps on disk, read the Claude Code history guide; for the controls around all of this, the AI coding agent security checklist. ranwhat watch has the full rule set, and the rest of the guides are listed here.
Sources
- Explore the .claude directorytranscript contents, retention sweep, Desktop and Cowork transcripts, plaintext storage
- Settings reference: cleanupPeriodDaysdefault, minimum, the sweep
- Manage sessionstranscript location, format, export
- Environment variablesCLAUDE_CONFIG_DIR, CLAUDE_CODE_SKIP_PROMPT_HISTORY
- Hooks guidethe command-log example, hook locations
- Hooks referenceinput fields, PostToolUseFailure, disableAllHooks
- Monitoring usageOpenTelemetry events and gates
- CheckpointingBash changes are not tracked
- Settings reference: requiredMinimumVersionmanaged only, 2.1.163 or later
- GHSA-gfvf-j8jh-jxxwCVE-2026-103012, fixed in 2.1.260
- 0DIN: Clone this repo and I own your machinethe error Claude followed
- Mitiga: stealing MCP tokens in Claude Codethe chain, the disclosure, what to watch
- ranwhat/watch.pythe nine rules and the JSON record
- ranwhat READMEwhat is not treated as an action, what is not read