An audit log for Claude Code: what the agent did, and when

A plain JSONL record of the tool calls your agent made and the ones it was stopped from making, plus a report you can read in a review.

When an agent works for twenty minutes while you do something else, you want a record of what it ran. Not a transcript. A list you can scan: which commands, which files, and what was stopped. This guide covers the audit log that the free agent-guardrails plugin writes, and how to use it.

Why keep a record

There are three practical reasons.

What the plugin writes

The plugin appends one JSON object per line to .claude/guardrails-audit.jsonl, relative to your project directory. This format is called JSONL. It is easy to read with jq, a spreadsheet or a few lines of Python. The plugin uses Claude Code hooks to write it. Install the plugin like this:

/plugin marketplace add RichFloForge/agent-guardrails
/plugin install agent-guardrails@agent-guardrails-marketplace

Logging is on by default and needs no config. Four kinds of event are recorded:

Here is a short example log:

{"ts":"2026-11-07T07:13:52Z","session_id":"abc123","event":"post","tool_name":"Bash","summary":"npm test","decision":"allow","rule":null,"command":"npm test"}
{"ts":"2026-11-07T07:13:55Z","session_id":"abc123","event":"post","tool_name":"Edit","summary":"src/app.py (120 chars)","decision":"allow","rule":null,"file":"src/app.py"}
{"ts":"2026-11-07T07:13:59Z","session_id":"abc123","event":"pre-bash","tool_name":"Bash","summary":"git reset --hard","decision":"block","rule":"destructive_shell","command":"git reset --hard","reason":"[agent-guardrails] destructive_shell: blocked - git reset --hard (discards uncommitted work). To allow: edit .claude/guardrails.json (add a regex to rules.destructive_shell.allow_patterns, or set rules.destructive_shell.mode to \"warn\")."}
{"ts":"2026-11-07T07:14:30Z","session_id":"abc123","event":"stop","tool_name":null,"summary":"session summary: 2 tool calls, 1 blocked, 0 warnings, 1 files touched","decision":"allow","rule":null}

The fields

Every record has these keys: ts (UTC timestamp), session_id, event, tool_name, summary, decision (allow, block or warn) and rule (the rule that fired, or null). Some records add more:

For edits, summary holds the path and the size of the new text, not the text itself. File contents are never logged.

Reading it with the report command

Raw JSONL is fine for searching. For a quick summary, run this inside Claude Code:

/agent-guardrails:report

The report covers the time range, the number of records and sessions, event counts, tool calls by tool, blocks by rule, warnings by rule, the most recent blocks, the files touched most often, and the most frequent commands. An excerpt looks like this:

# agent-guardrails audit report

- Time range: 2026-11-07T07:13:52Z to 2026-11-07T07:14:30Z
- Records: 4 across 1 session(s)
- Blocked tool calls: 1

## Blocks by rule

| Rule | Blocks |
|---|---|
| destructive_shell | 1 |

The underlying script also takes --json for machine output, --session ID to look at one session, --log PATH to read a different file and --top N to change the length of the top lists. Without --log, it reads the path set by audit.path in your config, which defaults to .claude/guardrails-audit.jsonl in the project directory.claude/guardrails-audit.jsonl in the project directory.

Using it in a code review

A short routine works well. Run the report before you open the pull request, or before you approve one from an agent.

  1. Check Blocked tool calls. Zero is fine. A few is normal. Many blocks for one rule means the agent keeps trying something your policy forbids.
  2. Read the Files touched list against the diff. A file in the log that is not in the diff may have been edited and reverted, or only read. A file in the diff that is missing from the log is worth a question.
  3. Skim Top commands for anything unexpected, such as a deploy command or a network call.
  4. Filter for decisions that mattered:
jq -c 'select(.decision == "block" or .decision == "warn")' .claude/guardrails-audit.jsonl

To count how often each file was touched:

jq -r 'select(.event == "post" and .file) | .file' .claude/guardrails-audit.jsonl | sort | uniq -c | sort -rn

Note that "touched" means any tool call with a path, so reads count as well as edits.

What it does not record

Be clear about the gaps before you rely on the log.

It is also a plain local file. Since version 0.2.1 the plugin refuses Edit and Write calls to the log whatever your policy says, but a shell command could still alter it, so nothing makes it tamper-evident. Do not treat it as evidence that would stand up in a dispute. It is a convenience for you and your reviewers.

Retention and gitignore

The plugin only appends. It never rotates or deletes the log, so decide your own retention. Delete it per project when it gets long, or archive it with your build artefacts.

Decide whether it belongs in version control. Commands can contain paths and internal hostnames, so the safe default is to keep it out of git. Add this line to .gitignore:

.claude/guardrails-audit.jsonl

If your team wants the log versioned for audit reasons, commit it deliberately and tell people what it contains. You can move it with the audit.path setting, or turn it off with audit.enabled set to false. In .claude/guardrails.json:

{ "audit": { "enabled": true, "path": ".claude/guardrails-audit.jsonl" } }

If you move the log, the report command follows the new path automatically; --log is only needed to read a different file.

Next, read how to block destructive commands in the first place, or see complete policy examples.

Related guides