Decision. Treat git as the undo and a PostToolUse hook writing outside the repo as the audit log. Claude Code’s built-ins are convenience, not evidence: transcripts are deleted after 30 days [1], and
/rewindcannot undo anything a bash command did, nor a subagent’s edits [4]. Then rehearse one restore — “a backup you have never restored from is not a backup, it is a theory” [12].
How you know what it did
| Layer | Gives you | Gap |
|---|---|---|
| Session transcript | JSONL per session at ~/.claude/projects/<project>/<id>.jsonl; /export for humans [1] |
30-day retention, format is internal and breaks between releases [1] |
claude -p --output-format json |
Result, session ID, usage, cost of one run as parseable JSON [1] | Per-run only, not a standing log |
PostToolUse hook |
Hook JSON on stdin — session_id, cwd, tool_name, tool_input, tool_result, transcript_path — append it anywhere [3] |
You write it; agent can rm it if writable |
| OpenTelemetry | CLAUDE_CODE_ENABLE_TELEMETRY=1 + OTEL_LOGS_EXPORTER emits claude_code.tool_decision / tool_result / api_request; add OTEL_LOG_TOOL_DETAILS=1 for actual commands and file paths [2] |
Needs a collector (Loki/SigNoz) |
| auditd | -a always,exit -F arch=b64 -S execve -k user-commands, query ausearch -k user-commands [7] |
Kernel-level truth, noisy and hard to read |
Cheap extras: HISTTIMEFORMAT to timestamp shell history [8], and script --log-timing to make a replayable typescript of a whole session [9].
⚠ Send the hook/OTel log to a path the agent cannot write — a systemd-journal socket, a Loki endpoint, or a Proxmox-side collector. An audit log inside the workspace is not an audit log.
How you undo it
Git first. Commit before every prompt; one worktree per task. git reflog recovers a bad reset --hard or force-push — but reachable entries expire after 90 days and unreachable ones after 30 [5]. ⚠ An agent allowed to run git can also rewrite history: a real report had git reset --hard run unauthorized, destroying hours of work → “the safeguards have to be outside the model” [6]. Deny git reset --hard, git push --force, git clean in permissions, and push to a remote the agent has no credentials for.
Snapshots second. Timeshift on btrfs is near-instant, but ⚠ excludes /home by default — where your code and ~/.claude live [10]. On Proxmox, qm snapshot <vmid> <name> --vmstate 1 then qm rollback <vmid> <name>; RAM-inclusive snapshots auto-start the VM on rollback [11]. Rollback is seconds-to-minutes — the cost is losing everything since the snapshot, so snapshot before an unattended run, not nightly.
The 5-step drill
qm snapshot 101 pre-agent --vmstate 1before an unattended session [11].- Let the agent do real destructive work:
rm -rf src/, thengit reset --hard HEAD~3. - Recover with git alone —
git reflog, checkout the pre-reset SHA [5]. Time it. - Now recover the file the agent deleted via bash, which
/rewindcannot restore [4]:qm rollback 101 pre-agent[11]. - Reconstruct what happened from the hook log alone, with
~/.claude/projectsdeleted [3]. If you can’t, your audit trail is the transcript — and it expires [1].