# How to Audit What Your Coding Agent Actually Runs (2026)

Four ways to answer 'what did the agent do while I wasn't looking' — harness transcripts, shell history, OS-level logging, and a hook-path audit log — and what each one misses.


<p><sub>Real recording. It ends on the answer this post is about: <code>grep '"event":"pre"' ~/.acp/audit.jsonl</code> — every call the agent made, its decision (deny / ask / allow), and why, in one line each, on-device. That receipt is the hook-path audit log (option #4 below), not a reconstruction from a transcript.</sub></p>

Every incident writeup has the same second act: after the deletion, the
force-push, the leaked file — the team tries to reconstruct what the agent
actually did, and discovers the answer lives in a scroll of transcript, if
it lives anywhere. "If I can't reconstruct what it did, I don't trust it
yet" is the whole requirement. Four ways to get there, and what each misses.

## 1. Harness transcripts

Claude Code writes session transcripts (JSONL under `~/.claude/projects/`),
Codex and Cursor keep their own equivalents. Real data, already on disk,
free. Three gaps: they're **per-tool** (three agents, three formats, three
places), they're **conversation-shaped** (finding the twelve commands inside
ten thousand lines of model turns is work), and they're **decision-free** —
a transcript says what the agent said it ran; it doesn't say what was
allowed, denied, or why.

## 2. Shell history

Tempting and mostly empty: agents execute through their own exec tools, not
your interactive shell, so `~/.zsh_history` misses nearly everything. Skip.

## 3. OS-level logging

`script`, auditd, EndpointSecurity on macOS — complete by construction, and
the right call in genuinely regulated environments. The costs: setup weight,
firehose output where agent calls are indistinguishable from your own, and
still no decision context. This is a forensics layer, not a daily answer to
"what did it just do".

## 4. A hook-path audit log

Every major harness fires a hook before and after each tool call. Log at
that choke point and you get the thing the other three can't produce: one
line per call, **across every agent you run**, with the decision attached.
This is the layer we build — one command, on-device, no account:

```bash
curl -sf https://agenticcontrolplane.com/install.sh | bash -s -- --local
tail -f ~/.acp/audit.jsonl
```

Each line: timestamp, client, tool, classified action (`Bash.git.push`,
`Bash.curl.api.stripe.com`), decision, the rule that decided, the reason.
The same file whether the call came from Claude Code or Codex. Because the
log and the policy share a path, "audit" and "control" stop being separate
projects — the line that records the force-push is written by the same hook
that [blocked it](/blog/block-dangerous-commands-coding-agent).

Stated limits: it sees what the hooks see — a harness without hook coverage,
or a command you run yourself in a bare terminal, isn't in the file. And
locally it's an honest record, not a tamper-proof one (see the FAQ). Teams
that need the agent-can't-touch-it version keep the same log in a
[workspace](/for-coding-agents) instead — same hook, the copy lands off-box,
priced per call.

## What we'd actually do

Solo, trusted repos: hook-path log locally, transcripts as backup detail.
Anything with untrusted input or more than one person: hook-path log
shipping off-box, OS-level capture if an auditor will ever ask. Either way,
the test stays the same: can you answer "what did the agent run today" from
one file, in one grep, with reasons — or are you re-reading transcripts and
hoping? The [60-second recording](/blog/control-your-coding-agent-in-one-command)
ends on exactly that grep.


