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.
- Review. A pull request shows the diff. It does not show the commands that produced it or the ones that were refused.
- Debugging. When something odd happens in a repo, a timestamped list of tool calls narrows down when it started.
- Tuning. If one rule blocks the same thing ten times, your policy or your prompts need work.
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:
post: written after a tool call completes, with a decision ofallow. This is your activity trail.pre-bashandpre-edit: written before a Bash command or a file edit, but only when a rule blocks the call or warns about it. Calls that pass the checks are not written at this stage; they show up aspostrecords.stop: written when the agent finishes a turn, with a one-line summary such as the number of tool calls, blocks, warnings and files touched in that session.warning: written when the plugin itself has a problem, for example an unreadable config file or hook input it could not parse.
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:
command: for Bash calls, the command, with secret-looking values redacted and cut to 200 characters.file: for tools that act on a path, such as Edit and Write, the file path.reason: for blocks and warnings, the message the agent was shown.
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.
- 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.
- 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.
- Skim Top commands for anything unexpected, such as a deploy command or a network call.
- 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.
- File contents. Only the path and a character count are kept. Use git for what changed.
- Command output and tool results. You can see what ran, not what it printed.
- Anything outside the hooks. Hooks only see tool calls Claude Code makes. A script that runs ten commands internally appears as one command.
- Passing pre-checks. Allowed calls are logged once, as
post, not twice. - Every secret. Redaction covers the plugin's built-in secret patterns, such as AWS keys, GitHub and Slack tokens, private key headers and quoted
api_keyorpasswordassignments. A secret in an unusual format, typed into a command, could be stored as written.
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.