claude-video-effects
51 video effects for Claude Code: drop in a talking-head clip, type /effects orbit. Face tracking, behind-your-head layers. By @vibha.in.progress
AgentTrail traces your agent's execution and automatically compiles repeat failures into permanent guardrails. Works with Claude Code, Cursor and Codex.
PluginsTypeScript · JavaScriptGitHub @agenttrailhqOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Observability and guardrails for your ai agents. AgentTrail Guard traces your agent's execution and automatically compiles repeat failures into permanent guardrails.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
AgentTrail Guard: open-source guardrails for AI coding agents
Check your AI coding agent's commands and file changes before they run.
Works with Claude Code, Cursor, and Codex CLI. Local, free, and no account needed.
AgentTrail Guard is a free, open-source CLI
that connects to your coding agent's hooks and checks every tool call that reaches them
against 74 inspectable guardrails before it runs. Guard can block a
matching call, send it to you for approval, or record it as a warning, depending on the
agent. When nothing matches, your agent's normal permission flow continues.Read the full README ↗Check your AI coding agent's commands and file changes before they run. Works with Claude Code, Cursor, and Codex CLI. Local, free, and no account needed.
Quick start · Guardrails · Reference · Changelog
AgentTrail Guard is a free, open-source CLI that connects to your coding agent's hooks and checks every tool call that reaches them against 74 inspectable guardrails before it runs. Guard can block a matching call, send it to you for approval, or record it as a warning, depending on the agent. When nothing matches, your agent's normal permission flow continues.
You need: Node.js 20 or newer, npm, and at least one of Claude Code, Cursor, or Codex CLI.
npm install -g @agenttrail/guard
agenttrail-guard init --agent claude # Claude Code
agenttrail-guard init --agent cursor # Cursor
agenttrail-guard init --agent codex # Codex CLI
Each setup is independent, and settings are shared under ~/.agenttrail/guard/. To see
exactly what init would change first, add --print; it changes nothing.
init demonstrates the engine by evaluating a synthetic rm -rf / and showing its
decision. The command is never executed. This checks the engine; you still need to
complete your agent's setup below.
/hooks, and approve each agenttrail-guard entry.
Guard does not run until you approve it.agenttrail-guard status
status shows what is enforcing in each agent, recent decisions, and settings that need
attention. For Codex, ON means the hook is installed and well-formed; confirm approval in
Codex's own /hooks screen, because Guard cannot read that state.
To upgrade, install the latest release, re-run init for each agent you use, and restart
that agent. Your settings are kept, and Codex keeps the approval you already gave. Before
1.0, a minor release can include a breaking change, so skim the changelog
first.
npm install -g @agenttrail/guard@latest
agenttrail-guard init --agent claude # and again for cursor or codex, if you use them
To remove Guard from an agent, run uninstall for it. It removes only what Guard added, and
keeps your settings in ~/.agenttrail/guard/.
agenttrail-guard uninstall --agent claude # or: --agent cursor, --agent codex
What each step changes is covered in Updating and Uninstall.
Prefer a guided walkthrough? See the AgentTrail Guard installation guide.
A real Claude Code session. The agent decides to reset the database to get a schema push
through; Guard blocks prisma db push --force-reset with dd.accept-data-loss before it runs.
Your coding agent runs shell commands and edits files all day, faster than you can review
each one. Most of those calls are exactly what you want. The ones that aren't, like
git reset --hard over a day of uncommitted work, rm -rf on the wrong path,
terraform apply -auto-approve, or a read of your .env, tend to be the ones you can't take
back. Guard puts a rule-based check on every tool call that reaches its hooks, in every
session, so the risky ones stop and explain themselves before they run, and everything else
carries on as normal.
~/.agenttrail/guard/.agenttrail-guard status shows recent decisions, and scan turns
past agent sessions into a self-contained HTML report.agenttrail-guard status shows recent decisions.Guard uses rule matching, not an LLM reviewing your code. It can stop a matching call only when your agent sends it through the installed hooks, it does not inspect file contents, and it does not guarantee that every dangerous action will be caught. On an internal error it fails open: your agent's normal permission flow continues. Read the coverage and runtime limits before relying on it.
The table below is real output: each row is a Claude Code PreToolUse call passed to
Guard's hook, and the reason Guard returned before anything ran.
| Your agent proposes | Guard's answer to Claude Code |
|---|---|
git reset --hard origin/main | Deny: agenttrail-guard blocked this: git reset --hard discards uncommitted work (guardrail wt.reset-hard) |
terraform apply -auto-approve | Ask: agenttrail-guard needs a person to approve this: Terraform apply/destroy without the confirmation prompt (guardrail pi.terraform-auto-approve) |
Read .env | Warn: agenttrail-guard is warning about this: Flag reading a .env file (guardrail block-env-file-read) |
npm test | No output. Claude Code's normal permission flow decides. |
git commit -m "docs: explain why rm -rf / is dangerous" | No output. Mentioning a command is not running it. |
In a real Claude Code session, an agent told it was only dev data reached for
npx prisma db push --force-reset, and dd.accept-data-loss blocked it before it ran.
Read the case study.
Every decision names its guardrail, and every guardrail explains itself (the description is wrapped and trimmed here):
$ agenttrail-guard guardrails show wt.reset-hard
wt.reset-hard working-tree · severity: high
git reset --hard discards uncommitted work
Action block (shipped default)
Enforcing yes
Discards every uncommitted change in the working tree, irrecoverably — there is no
reflog for work that was never committed. Does NOT match `git restore` (see
wt.restore-path), `git checkout -- .` (see wt.checkout-discard) [...]
Guard ships with 74 guardrails across 11 packs. Packs are named for the harm they prevent, not the technique used to spot it. By default, 13 guardrails block, 50 ask, and 11 warn.
| Pack | Rules | Representative examples | Defaults |
|---|---|---|---|
working-tree | 9 | git reset --hard, git clean -fd, force-push, rm -rf | 5 block, 4 ask |
destructive-data | 8 | rm -rf on an absolute path, dropping a database, destructive SQL, deleting Docker volumes | 5 block, 3 ask |
prod-infra | 8 | terraform apply -auto-approve, kubectl delete, Helm uninstall or rollback, production config edits | 8 ask |
secret-exposure | 10 | Reading .env or credential files, printing tokens, hard-coded secrets in commands, public bucket ACLs | 1 block, 4 ask, 5 warn |
rce-supply-chain | 6 | Piping curl into a shell, running code from a URL, TLS verification off, a redirected registry | 1 block, 5 ask |
safety-bypass | 7 | --no-verify, admin merges past branch protection, disabling hooks, erasing shell history | 6 ask, 1 warn |
privilege-supply-chain | 6 | sudo writes, chmod 777, IAM grants, publishing packages, new dependencies | 5 ask, 1 warn |
file-scope | 4 | Writes to system directories, git internals, CI definitions, the agent's own config | 4 ask |
agent-context | 6 | Editing agent instructions or memory, adding MCP servers, installing skills, turning off approvals | 6 ask |
test-integrity | 6 | Deleting tests, blanket snapshot updates, bypassing coverage gates, skip-CI markers | 2 ask, 4 warn |
exfiltration | 4 | Reverse shells, public tunnels, file uploads, paste services | 1 block, 3 ask |
Run agenttrail-guard guardrails list for every rule with its current action, or
guardrails show <guardrail-id> for one rule in full. guardrails list --owasp ASI06 lists the
guardrails tagged with one OWASP risk (ids from the 2026 lists: the OWASP Top 10 for Agentic
Applications and for LLM Applications 2026). A tag names the risk a guardrail addresses where it
becomes a command or a file change, not full coverage of it. Only shipped guardrails carry tags.
The source for every rule is in agenttrailhq/guardrails. To browse
every rule online, or paste a command and see which rules match it, use the
guardrail library.
@agenttrail/guard) is the tool: the agent hooks,
the matching engine, the CLI, the local decision log, and the scan report.@agenttrail/guardrails) is the rule library: plain data, with matching and non-matching
examples for every rule.Guard bundles the library, so you do not need to install it separately. Rules update when you update Guard; nothing is fetched at runtime.
A rule's action decides what happens when it matches. Each agent's hook system supports a different subset, so the same action can land differently:
| Action | Claude Code | Cursor | Codex CLI |
|---|---|---|---|
block | Blocks matching hooked calls, with the guardrail's reason | Blocks matching hooked calls | Blocks matching hooked calls; Codex shows the reason |
ask | Requests approval; broad permission allow rules can suppress the prompt | Requests approval for terminal commands; blocks other calls that need approval | Blocks calls that need approval, because Codex runs the action anyway when a hook asks |
warn | Allowed, with a warning message | Allowed with no message; recorded locally | Allowed; shown in the terminal UI, not in codex exec |
Every match is recorded in the local decision log, which agenttrail-guard status reads.
A guardrail that fires on something legitimate should get narrower, not switched off.
status identifies the rule that fires most often and suggests a command to allow that
specific pattern. Start by reading the rule:
agenttrail-guard status
agenttrail-guard guardrails show <guardrail-id>
For a repeat false positive, prefer a narrow exception. allow silences only the named rule
for matching commands; the rule keeps enforcing everywhere else, and every other rule is
untouched. Review the pattern status suggests before applying it.
$ agenttrail-guard guardrails allow require-approval-rm-rf "rm -rf ./tmp"
Allowlisted. require-approval-rm-rf no longer matches "rm -rf ./tmp".
The guardrail still fires on everything else, and the other 73 guardrails are unchanged.
It refuses a pattern broad enough to disable the rule quietly:
$ agenttrail-guard guardrails allow require-approval-rm-rf "*"
agenttrail-guard guardrails allow: refusing that pattern — it also matches `ls -la`, `git status`, `pnpm install` and 3 more, which this guardrail has nothing to do with. That is a global mute for this guardrail, not a silence for one shape — the guardrail would keep showing as enabled while enforcing nothing.
agenttrail-guard guardrails set-action <guardrail-id> warn # record, don't stop
agenttrail-guard guardrails reset <guardrail-id> # back to the shipped default
agenttrail-guard guardrails reset --all # undo every change you made
warn records the match without blocking the call. reset --all never touches rules you
added yourself.
All bundled packs are enabled by default. Your changes take effect on the next tool call. A new pack shipped in an update is enabled unless you disable it.
Write a rule with at least one command it must match (block) and one it must not
(allow):
{
"id": "team.deploy-prod-script",
"category": "prod-infra",
"severity": "high",
"defaultAction": "require_approval",
"title": "Our production deploy script",
"description": "Holds `pnpm deploy:prod` for approval. Does not match the staging script.",
"match": {
"any_of": [
{
"kind": "execute_tool",
"label": "Bash",
"detail_matches": ["\\bpnpm\\s+(run\\s+)?deploy:prod\\b"]
}
]
},
"fixtures": {
"block": ["pnpm deploy:prod", "pnpm run deploy:prod"],
"allow": ["pnpm deploy:staging"]
}
}
label is the tool name: one name ("Bash"), or an alternation of two or more in braces
("{Bash,PowerShell}"), and * works as in a glob ("mcp__github__*"). A single name in
braces ("{Bash}") is not an alternation: its braces are read literally, so it never matches
Bash. guardrails add writes it without the braces and says so, and guardrails validate
warns about it. Only a plain tool name is rewritten: anything else in one-value braces, such
as a wildcard or a negation ("{*}", "{!Bash}"), is only warned about, because without the
braces it could match almost every tool call.
Validate it against Guard's own evaluator, then add it:
$ agenttrail-guard guardrails validate my-rule.json
Valid. team.deploy-prod-script (ask)
block fixture pnpm deploy:prod → matched PASS
block fixture pnpm run deploy:prod → matched PASS
allow fixture pnpm deploy:staging → not matched PASS
Schema checked with the same `parseRule` CI runs. Fixtures were run through the
guard's own evaluator — the one that will run on your machine.
$ agenttrail-guard guardrails add my-rule.json
In JSON, "ask" is written require_approval. The CLI accepts ask, but a hand-edited file
does not. An owasp list on your own guardrail is ignored: only shipped guardrails carry OWASP
ids, and guardrails add and guardrails validate say so when they see one. See
Managing guardrails for every rule command, and the
guardrail format
for all fields.
agenttrail-guard guardrails export <id> turns one of your guardrails into a JSON draft for the
public library, with blanks marked FILL IN for its examples and its OWASP ids. Fill them in,
check the draft with validate, then run export on the draft to get the library file and the
steps to submit it. Both steps write to the current directory, or to --out <dir>. The
guide to submitting a guardrail
walks through it.
Your agents already keep session transcripts on disk. scan replays the recorded tool
calls through your current guardrails and writes a local report:
agenttrail-guard scan --agent claude # reads ~/.claude/projects
agenttrail-guard scan --agent cursor # reads ~/.cursor/projects/*/agent-transcripts/
agenttrail-guard scan --agent codex # reads ~/.codex/sessions
You get a summary in the terminal and a single self-contained
agenttrail-guard-report.html, opened in your browser (--no-open to skip, --out <file>
to choose where, --json for JSON only). The report loads nothing from outside itself.
The report opens with what Guard would have done: how many actions it would have blocked, asked you about, or warned on. It then lists the guardrails to fix first, the OWASP risks the matched guardrails name, every matched guardrail with filters (by action and by OWASP risk) and sort, repeated command shapes, next steps, and a suggested post. The post carries counts only, one per line between a fixed opening and the command that runs the same scan: the verified counts that may lead the report, as many as fit, or otherwise how many actions it would have blocked, asked about and warned about. It names no guardrail, and its Post on X link carries the text and nothing else. Each OWASP id shown is one the guardrail carries itself; the section covers coding-agent actions only, not full OWASP coverage. Its PDF and JSON buttons save the report as shown. One inline script runs the filters, copy buttons and exports. The page's Content-Security-Policy allows that script and blocks the page from loading or fetching anything. Links to agenttrail.sh carry UTM tags naming the report and the agent, and open only when you click them. The two OWASP lists are linked where OWASP publishes them.
A scan shows what would match now, not what was blocked then. It applies today's guardrails and settings to past calls, so its results can differ from what Guard enforced during those sessions, or from sessions that ran before Guard was installed.
Reports redact secrets, paths, and known identifying values, but redaction is not a
guarantee. Use --review and read the report before sharing it. The review output can
include raw MCP values that the report itself redacts. Details:
Seeing what already happened.
scan also checks the matches against this machine, read-only and offline. The checks
run by default; --no-checks skips them. Nothing is written but the report and its local
fixes file, nothing is sent, and no command from a session is run again. They read only:
git (rev-parse, ls-files,
for-each-ref, log, stash list), never through a shell, and the repo's git-tracked
files, each at most 1 MB;CLAUDE.md, CLAUDE.local.md,
AGENTS.md, GEMINI.md, .cursorrules, .windsurfrules and
.github/copilot-instructions.md at the top of each repo; the agent's own
(~/.claude/CLAUDE.md or ~/.codex/AGENTS.md); and the ones the sessions wrote. In
~/.claude, ~/.codex and ~/.cursor only .md and .mdc files are read. Only whether
a file tells the agent to skip checks is kept, never its text;autoUpdate) is kept;ps, or tasklist on Windows), never their
arguments. The list is cut down to known agent apps and tunnel programs as it is read, and
only the tunnel programs, such as ngrok or cloudflared, are counted;The report and --json carry counts only, never these paths.
A credential counts as likely real only on evidence: a known token format (an AWS key,
GitHub, GitLab, Slack, npm, Stripe, OpenAI, Anthropic, Google API, Hugging Face or SendGrid,
a private key, or a JWT that decodes; one for the anon role is public by design and does
not count), a credential's name with a value shaped like a credential, or a random-looking
value right after a credential's name, flag or header. A random-looking string alone does
not count, wherever it appeared; it is counted apart, as set aside. Text Claude Code adds to
a prompt itself, such as a reminder or a command's output, is told apart from what you typed.
No value is ever checked with the service that issued it.
The sessions are read on up to four worker threads in the same process: each worker parses
its share of the transcripts and evaluates their tool calls against your guardrails, then
reads them for the secrets check while the other checks run. What the workers hand back
never leaves the process. While the scan runs, it shows which phase it is in on stderr;
--quiet turns that off.
Every claim on a card comes from a check's result. A matched command counts as ran only
when the session recorded a result that shows it ran; one with no result either way may have
run, and is not counted as ran. Where a command that ran, or may have run, uses a form the
check does not read inside (a shell given a string to run or one this scan does not read,
inline code or a piped script, find -exec, xargs, process substitution, a command
substitution, eval, or git with its own -c settings, running a command it was given, or
after a hook or its config was written, a quote left open, or a command of that check's kind
behind a program it could not read), the card says could not confirm for that category,
never none or clean.
Each card shows its numbers and its fix first. A card that could not confirm everything ends with one line, in the same words on every card, saying how many commands that ran, or may have run, used such a form. The forms themselves are listed once, at the bottom of "Also checked on this machine", not on each card.
The checks read past these wrappers before a program, with their options: sudo, doas,
env, command, exec, time, nice, ionice, nohup, setsid, caffeinate, stdbuf,
timeout, gtimeout, chronic, unbuffer, taskset, chrt, flock, watch -x,
runuser -u, npx, bunx and dotenv; and these launchers, to the command they run:
aws-vault exec, op run, doppler run, mise exec --, direnv exec, uv run,
uv tool run, bun x, poetry run, pipenv run, pdm run, bundle exec, pnpm exec and
dlx, yarn exec and dlx, and npm exec. python -m awscli reads as aws. Behind any
other program no check reads, each later word that names a program a check reads
(my-wrapper git push origin main) is read again from there with that check's own reading,
and a command it finds makes the card say could not confirm, never counted and never clean.
An awk script that calls system() or pipes to a command, and a sed script with its e
command or flag, also make it say could not confirm. Some things stay out of reach: a wrapper
that hides the program in a string (su -c, env -S, nix-shell --run) is read as a form
the check cannot read, so the card says could not confirm without knowing what ran; and a
command handed as one quoted string to a program no check reads, a command run inside a
container, or a program reached only through a script file (./deploy.sh) is not read at all,
so a card can read clean.
Cursor's session files record no command output, so on Cursor a check of commands that ran that would read clean says could not confirm instead. The report leads with a verified count only when a recorded result backs it, and otherwise leads with the totals. A card that could not confirm never leads, with one exception: a count git itself confirms (commits on main without their checks, deleted work still recoverable) may lead, since what could not be read could only add to it, and its card still says it could not confirm all of it.
Codex: limited and unverified. For Codex, the checks:
On every Codex scan that runs the checks, the terminal summary, the report and each
git-backed card say all four points, in the same words; so does scan --help. A Codex scan
with --no-checks makes no claim about the checks.
A credential pasted into a prompt or printed by a command stays in the agent's transcript.
scan --scrub replaces the ones that look real with [REDACTED-BY-AGENTTRAIL:<kind>]:
agenttrail-guard scan --agent claude --scrub # dry run: says what it would replace
agenttrail-guard scan --agent claude --scrub --apply # makes the change
Rotate the credential first, then scrub. Taking it out of a transcript does not undo
the exposure. Scrubbing runs from the CLI only: the Claude Code plugin's scanner leaves it
out and refuses --scrub and --apply.
--apply it lists each file with how many values of each
kind it would replace, never the values, and changes nothing.--apply refuses while a process of that app is running, or when the list of running
programs cannot be read in full, and skips any file changed in the last 5 minutes. There
is no flag to override either. The first check goes by process name, so it cannot see an
agent running under a generic name such as node; the second check is there for that
case.--apply copies every file it will change to
a new folder under ~/.agenttrail/scrub-backups/ (0700, files 0600), with a manifest
and a restore command for each file. The backup still holds the credentials: delete
it once you have checked the scrubbed files. If any backup fails, nothing is changed. A
backup folder reached through a symbolic link is refused.
Each restore command is cp -p -- with both paths absolute and quoted. For a path holding
a control character, a mark that reorders text on screen, or a character that shows as
nothing, no command is made: the output names the backup file to copy back by hand, and
shows each such character as ?.--apply exits 1.| Command | What it does |
|---|---|
agenttrail-guard init --agent <claude|cursor|codex> | Install the integration, seed settings, and demonstrate the engine. Safe to re-run. |
agenttrail-guard status | Inspect installation state, recent decisions, and settings that need attention. |
agenttrail-guard guardrails | List, inspect, customize, and validate guardrails. |
agenttrail-guard scan --agent <claude|cursor|codex> | Check past sessions and create a local HTML report. With --scrub, replace likely-real credentials in that agent's transcripts; a dry run unless you add --apply. |
agenttrail-guard crash-report | Manage opt-in crash reports. Off by default. |
agenttrail-guard uninstall --agent <claude|cursor|codex> | Remove that integration and keep your settings. |
Run agenttrail-guard --help for CLI help, or agenttrail-guard guardrails --help for rule
management.
Guard runs in all three agents through each agent's own hooks, so coverage differs by agent.
These are the behaviors documented and measured in this repository; see the detailed limits
before relying on an integration. How each agent handles block, ask, and warn is in
Block, ask, and warn.
| Agent | Setup and coverage notes |
|---|---|
| Claude Code | Installed as a plugin. Coverage limits. |
| Cursor | Use an editor window. Sandbox and allowlist settings can bypass checks. Cursor cloud agents are outside its coverage. Cursor's limits. |
| Codex CLI | Approve every entry in /hooks. Measured on 0.154.0; Windows and Codex Desktop are unverified. Codex Cloud is outside its coverage. Codex's limits. |
scan also reads the transcripts already on your disk.~/.agenttrail/guard/, capped
at 1 MiB, with older records dropped past 30 days. Clear it with
agenttrail-guard status --clear-history.Scrubbing catches common secrets but is not complete: see the limits below and Files it keeps. The AgentTrail website summarizes what Guard reads, keeps, and sends.
Guard depends on the hooks and permission behavior of each agent. It cannot check actions that bypass those hooks or detect dangerous content inside a written file. In short:
ask can be skipped by your agent's own allow settings, and becomes a block on Codex.Read this before you rely on it. A security tool that overstates its coverage is worse than one that names the hole.
WebFetch is not
intercepted, and there are no website guardrails in v1. The underlying engine can
match on a command string and on a file path (it has no matcher for a URL), so a
URL guardrail would have validated, installed, and then matched nothing, forever,
without telling anyone. Rather than ship a channel whose guardrails only appear to
work, v1 leaves web traffic out and says so here. WebSearch is covered,
because a search query is ordinary text on the command channel.PreToolUse hook, as Cursor preToolUse and beforeShellExecution hooks, and as
Codex PreToolUse and permission-request hooks. Anything the agent does outside a
hooked tool is invisible to it, and the other two apps have more of these gaps than
Claude Code: see Cursor's limits and
Codex's limits.Write or Edit decides only by where the write lands, never by what it
contains, and write-then-execute falls in the gap: an agent can write a script (judged
by its path alone, its contents unseen) and then run it as a separate command. The run is
checked like any command, but if that command does not itself match a guardrail
(./setup.sh, say), the dangerous content that went into the file was never inspected.
Guard what a file's contents would DO by guarding the commands that act on it; a file-path
guardrail is a scope rule, not a content scanner.require_approval (shown as ask) returns Claude Code's ask decision, but Claude
Code's own permissions.allow rules win over a hook: if your settings.json allows a
tool outright ("Bash", "Read", "Edit"), a call to that tool runs with no prompt,
and the hold never reaches you. The guard cannot change this, because Claude Code
decides, and the hook cannot see that it happened, so the call is still recorded as a
hold. status and read your and tell you how many holds this
silences, naming the tools; a scoped allow like is narrower and is not
counted.Cursor's hooks see less than Claude Code's. In Cursor, the guard does not check these:
Grep is checked on its folder, and on the folder
joined to its glob, so a search for **/.env under a project is checked as that path.
A brace glob such as {.env,.env.local} is not expanded, and searching the contents of
files under a folder is not covered: the search pattern is not read.status reminds you.beforeShellExecution. It does not enforce an
ask from preToolUse, so a guardrail that asks for approval of a file edit, a read,
a Grep, a Delete or an MCP call blocks that call instead, with the message
"agenttrail-guard needs a person to approve this, and Cursor cannot ask: " and the
guardrail's title and id.beforeShellExecution entirely, so the guard never evaluates it and there is no
events.jsonl record either. A command the sandbox cannot run (one that needs the network,
or writes outside it) is escalated to approval, where the guard does check it; so coverage
under a sandbox run mode is partial, and decided by Cursor, not by the guardrails. For the
guard to check every terminal command, use a non-sandbox run mode (Cursor's allowlist
mode without the sandbox), and keep the commands you want checked off Cursor's command
allowlist. Unlike Claude Code's settings.json, there is no documented, stable file for the
guard to read the run mode from, so status and init name the effect but cannot count it.
When the hook does run, the decision is recorded in events.jsonl.The guard's Claude Code plugin inside Cursor. With Cursor's "Include third-party plugins" setting on, Cursor can run the guard's Claude Code plugin and hand it Cursor's payload. The plugin reads that as a Cursor call, and it still blocks what a guardrail blocks.
~/.cursor/hooks.json, the plugin
leaves approvals and the decision log to them, so each decision is asked for and
logged once.A deny reaches Codex intact: the command never runs, and Codex prints the guard's own reason on screen and the model repeats it. What differs is what the hook can see, what it can ask for, and what happens before you approve it. Everything below is measured on codex-cli 0.154.0.
/hooks. An installed-but-unapproved
entry never fires. The command runs normally, no decision is recorded, and neither
Codex nor the guard mentions it: the only symptom of an unguarded machine is
silence. status cannot close this gap for you, because approval is recorded in a
file the guard does not read.~/.agenttrail/guard/config.json, or with
guardrails set-action <id> warn, if a block is too strong for your work.cat, a sed -i, a redirect) arrives as an
ordinary command with no path field anywhere, so only the command guardrails apply to
it. Every app here has this gap; it is wider on Codex because the shell is the route
for more of what the agent does.codex exec. In Codex's terminal UI a warning shows as
a hook line above the command. In a scripted run the text appears
nowhere at all: only that the hook completed. The match is still written to
and counted by , which is where a warning is reliably read.Guard is a complete, standalone tool. You can install it, use it, and contribute to it without an AgentTrail account. Installing Guard does not connect your sessions to AgentTrail OS.
AgentTrail also makes AgentTrail OS, for developers and teams who also want a searchable record of every agent session. Both products use the same open rule library.
| AgentTrail Guard | AgentTrail OS | |
|---|---|---|
| Where decisions are made | On your machine, from the bundled library | On AgentTrail's servers, from your organization's policies |
| Account | None | Required |
| Agents | Claude Code, Cursor, Codex CLI | Claude Code, Cursor |
| Your own rules | A local JSON file | Authored in the app |
| History | A capped local log, plus on-demand scan reports | Searchable session history |
| For teams | One developer, one machine | Synced rules, policy backtesting, approvals, and an audit trail, depending on plan |
| When something breaks | Fails open: your agent's normal permission flow continues | Fails closed: the action does not proceed |
Not sure which fits? See AgentTrail Guard vs AgentTrail OS for a detailed comparison, or get started with either.
init, status, scan and guardrails add can end with one line about AgentTrail OS,
printed after their own output. Each line uses your own numbers. For example, status
shows it only when Guard asked about or blocked an action in the last 7 days, and scan
shows it only when the scan matched something. There is no line for Codex CLI, which
AgentTrail OS does not support yet.
guardrails add, when it has already shown today or .product-line cannot be
written, a line naming guardrails export takes its place. You never get both.To turn it off, and the export line with it, pass --quiet (or --json where a
command has it), or set "productLine": false in ~/.agenttrail/guard/config.json.
The setting covers these terminal lines only; it does not change the scan report.
| What you see | What to check |
|---|---|
agenttrail-guard is not found | Check that Node.js 20+ is installed and your npm global executable directory is on PATH. Reopen the terminal after installation. |
init's demo works, but the agent is not checked | Finish the agent-specific setup in Quick start. The demo checks the engine, not the live hook connection. |
status reports BROKEN | Read the named problem and re-run init --agent for that agent. This refreshes the hook copy and its Node path. |
Codex reports ON, but calls are not checked | Open /hooks in Codex CLI and approve every Guard entry. ON reports installation state, not approval. |
An ask rule never prompts in Claude Code | A broad permissions.allow entry in settings.json can skip the prompt. status names the tools affected. |
| A legitimate command keeps matching | Inspect the rule with guardrails show, then add a narrow exception or change its action. See Tune a guardrail. |
| A scan differs from a past decision | Scans apply your current guardrails to recorded calls; rules and settings may have changed since the session. |
If the issue persists, open a Guard issue with the details listed in CONTRIBUTING.md.
Can I use Guard in a team? Teammates can each install Guard and share custom rule files through your own repository. Guard's settings are per developer and per machine; nothing syncs. AgentTrail OS is built for shared policies.
What if I already use the agenttrail Claude Code plugin?
init --agent claude detects it and stops, rather than installing a second hook that
decides on the same tool call.
Found a false positive, a missed command, or have an idea? Contributions of every size help, code or not. CONTRIBUTING.md explains which repository each report belongs in, how to build and test Guard, and how the code is laid out. Please follow the Code of Conduct, and report vulnerabilities privately as described in SECURITY.md.
The full reference lives in this README, and most sections are collapsed so you can open just the one you need:
status shows.If you also have AgentTrail OS's agenttrail plugin installed, init --agent claude
installs anyway and tells you so. Guard's hook stands down in the projects where
AgentTrail OS setup recorded enforcement on this machine, so one hook decides there,
and Guard protects every other project. In a project set up before that record
existed, run AgentTrail OS setup again, or both hooks decide.
Claude Code. init --agent claude does two things, and nothing else. It writes its
own two files under ~/.agenttrail/guard/, and it asks Claude Code to install the hook
as a plugin. It never touches the hooks block in your settings.json; Claude
Code owns that file, and the guard's hook lives inside the plugin instead.
Cursor. init --agent cursor writes the same two files, copies the hook to
~/.agenttrail/guard/cursor/guard-hook.mjs, and adds two entries to Cursor's user hooks
file, ~/.cursor/hooks.json: one under preToolUse and one under
beforeShellExecution. Each runs the copy with the Node that ran init, named by its
absolute path, with a 10-second timeout. It does not run Claude Code.
~/.cursor/hooks.json is kept, in order.version
other than 1, has a hook list that is not an array, or is read-only..cursor/hooks.json or an enterprise hooks file.status reports the install as BROKEN; run
init --agent cursor again.Cursor reloads hooks.json by itself. If the guard's entries do not show in Cursor's
Hooks tab, restart Cursor. Use Cursor's agent in an editor window, and read
Cursor's limits before you rely on it.
Codex CLI. init --agent codex writes the same two files, copies the hook to
~/.agenttrail/guard/codex/guard-hook.mjs, and adds the guard's entries to Codex's
user hooks file, ~/.codex/hooks.json: one for the pre-tool event and one for the
permission-request event. Each runs the copy with the Node that ran init, named by
its absolute path. It does not run Codex.
~/.codex/hooks.json is kept, in order, and the
guard's entries are appended, never inserted. Codex identifies an approval by
an entry's position in the file, so moving one would revoke it.[hooks] table
in ~/.codex/config.toml, and the two sources add together rather than override,
so an entry in both would run the guard twice on every call..codex/hooks.json or an enterprise-managed hooks file.status reports the install as BROKEN; run
init --agent codex again.Then approve the guard inside Codex, or none of this runs. Start Codex, type
/hooks, and approve each agenttrail-guard entry; Codex reviews each one
separately. Until you do, the install is complete and completely inert: the hook
never fires, every command goes through unchecked, and neither Codex nor the guard
says a word about it. The guard cannot approve itself, because writing Codex's trust
record for it would defeat the review that record exists for. Measured on codex-cli
0.154.0, and it applies again after any change to an entry; read
Codex's limits before you rely on it.
init finishes by running a synthetic rm -rf / through the real guardrail engine and
showing you it being blocked. Nothing is executed; it is a demonstration, so you can
see it work before anything real runs through it.
Want to look before you leap? npx @agenttrail/guard@latest init --agent claude --print
(or --agent cursor --print, or --agent codex --print) shows you exactly what it
would do and changes nothing.
A new release can add guardrails and whole packs. Every pack is on unless you turned it off, so a pack added in a release you install starts enforcing with no step of its own. The update itself is two steps, for each app you use:
Get the new release and re-run init with it. Your settings in ~/.agenttrail/guard/
are kept.
npx @agenttrail/guard@latest init --agent claude # Claude Code
npx @agenttrail/guard@latest init --agent cursor # Cursor
npx @agenttrail/guard@latest init --agent codex # Codex CLI
If you installed the command globally, run npm install -g @agenttrail/guard@latest,
then agenttrail-guard init --agent claude, agenttrail-guard init --agent cursor and
agenttrail-guard init --agent codex.
Restart the app. Claude Code runs its own cached copy of the plugin and keeps running the old one until it restarts; restart Cursor too if the guard's entries do not show in its Hooks tab.
A new release does not cost you a second Codex approval. Codex trusts the entry (the command, its timeout and its matcher) and not the script that entry runs, so a release that ships a new hook keeps the approval you already gave. That is also why the entry is frozen: changing it would silently revoke the approval and leave you unguarded until you noticed. See Codex's limits.
status tells you when a step was missed: it names the version Claude Code is running when
that differs from the guard you have installed, and says when the Cursor or Codex install
was made by a different version. If the app is running a newer guard than the command you
ran status with, it says that command is the one out of date, rather than suggesting an
init that would downgrade the app.
npx @agenttrail/guard@latest uninstall --agent claude
npx @agenttrail/guard@latest uninstall --agent cursor
npx @agenttrail/guard@latest uninstall --agent codex
For Claude Code it removes the guard's plugin, and only that. For Cursor it removes only
the guard's two entries from ~/.cursor/hooks.json, and for Codex only the guard's
entries from ~/.codex/hooks.json:
It then deletes the hook copy and the other files under ~/.agenttrail/guard/cursor/ or
~/.agenttrail/guard/codex/. All three are safe to re-run, and all three leave your
settings in ~/.agenttrail/guard/.
One thing to know before uninstalling from Codex: removing the guard's entries moves
every entry after them up the file, and Codex identifies an approval by position. Any
other tool's hook that sat below the guard's therefore needs approving again in
/hooks. uninstall says so when there is one.
status showsstatus takes no --agent. It prints a section for Claude Code, one for Cursor, one
for Codex CLI, and then what applies to all three.
Claude Code
Claude Code: not found when the claude command cannot be started, and then no
plugin state is read.Enforcement: ON with the number of guardrails and how many of the library's
packs are on (across 11 of 11 packs), OFF when the plugin is installed but
disabled, or NOT INSTALLED with the command that installs it.Active bundle:), and a line
when that copy is a different version from the guard running status: when the copy
is older, the command that refreshes it and the reminder to restart Claude Code; when
it is newer, that the command you ran is out of date.PROBLEM line when the plugin's recorded source is missing, with the one command
that fixes it.Cursor
Enforcement: ON when both of the guard's entries are in ~/.cursor/hooks.json, and
the Node and the hook copy they run both exist.NOT INSTALLED when there is no guard entry, with the command that installs it.BROKEN when only one of the two entries is there, an entry runs a command in a form
the guard does not write, the Node or the hook copy is missing, or the file cannot be
read as a hooks file. It names the first problem it finds and tells you to run
init --agent cursor again. A Cursor hook that cannot run lets every call through and
shows nothing in Cursor, so this is where you find out.ON or BROKEN: a line when the install was made by a
different guard version from the one running status, and a reminder to use an
editor window, because Cursor's Agent Window can skip hooks.Codex CLI
Enforcement: ON when the guard's entries are in ~/.codex/hooks.json, and the
Node and the hook copy they run both exist.NOT INSTALLED when there is no guard entry, with the command that installs it.BROKEN when only some of the guard's entries are there, an entry runs a command in
a form the guard does not write, the Node or the hook copy is missing, or the file
cannot be read as a hooks file. It names the first problem it finds and tells you to
run init --agent codex again.init put it. Codex identifies an
approval by an entry's position in the file, so an entry another tool pushed down
reads perfectly and is no longer trusted.~/.codex/config.toml, which the guard does not
read: it ships no TOML parser and will not grow one to read a file it never writes.
ON here means installed and well-formed; whether it is running is answered only
by Codex's own /hooks screen.ON or BROKEN: a line when the install was made by a
different guard version from the one running status.All three
config.json that were ignored,
including a pack name in disabledPacks that matches no pack, and an enabledPacks
key left by an older release, which is no longer read.claude,
cursor or codex), the guardrail id and the command; then the guardrail that fired
most, with the one line that silences it for that command.When one guardrail fires repeatedly on something legitimate, agenttrail-guard guardrails
fixes it without hand-editing JSON or uninstalling.
guardrails list [--pack|--owasp|--enabled|--disabled] what is on, and what is not
guardrails show <guardrail-id> everything about one guardrail
guardrails enable|disable <guardrail-id|pack> turn a guardrail or a whole pack on/off
guardrails set-action <guardrail-id> block|ask|warn change what a guardrail does
guardrails add <file.json> install a guardrail you wrote
guardrails remove <guardrail-id> delete one of your own guardrails
guardrails allow <guardrail-id> <pattern> silence ONE guardrail on ONE shape
guardrails reset [<guardrail-id>|--all] undo your changes
guardrails validate [<file>] the same check CI runs
guardrails export <guardrail-id|draft.json> share one of yours with everyone
Shared flags: --json, --quiet, --config <dir>.
list filters: --pack <name>, --owasp <id>, --enabled, --disabled.
allow is the one that matters. It suppresses one guardrail on one command shape
and nothing else: the same guardrail keeps blocking everything else, and the others are
untouched. agenttrail-guard status names the guardrail that has been firing most and
prints the exact line to paste.
It will refuse a pattern that would quietly do more than you asked. *, ** and
anything starting with ! match every command, so allowlisting one would disable that
guardrail entirely while guardrails list still showed it enabled. It also refuses a
pattern that is one of the guardrail's own block fixtures (a command it exists to stop)
because silencing that shape would blind the guardrail to its own purpose. Patterns are
matched in /-separated segments, so no * crosses a /; give ** a segment of its own
(./generated/**) to cover a subtree.
One word to get right. You type ask, and the tool prints ask, but the value
stored in config.json and guardrails.json is require_approval. That is the vocabulary
the guardrail engine uses. Writing "ask" into either file by hand does not work:
guardrails.json reports it as invalid, and config.json silently ignores it, which is
why status now lists settings it had to drop.
Turning packs off. guardrails disable <pack> adds the pack to disabledPacks in
config.json, and enable takes it out; the change applies to the very next tool call.
You can turn every library pack off; the guard then checks only your own guardrails, if
you have any, and says so. guardrails reset --all puts everything back: it clears action
overrides, allowlist entries, guardrails you turned off, and packs you turned off, removes an
enabledPacks key an older release left behind, and states the pack change on a line of its
own. It never touches your own guardrails in guardrails.json.
agenttrail-guard scan --agent claude
agenttrail-guard scan --agent cursor
Claude Code and Cursor already keep a record of every agent session on your disk.
scan --agent claude reads Claude Code's transcripts under ~/.claude/projects, and
scan --agent cursor reads Cursor's session files under
~/.cursor/projects/*/agent-transcripts/. It replays every tool call through the same
guardrails the hook enforces, and tells you what your agent has actually been doing:
how many sessions, which guardrails would have fired, which mistakes repeat, and, for
Claude Code, exact token counts with the cache split.
scan --agent codex reads session files under ~/.codex/sessions.
Use --dir <root> to select a different transcript root for any agent.
The Claude Code and Cursor readers open each session's own file and the sub-agent
transcripts beside it, so a sub-agent's tool calls are counted in the session that started
it; any other .jsonl file elsewhere under the root is not read, and the report counts it.
Cursor's session files hold less than Claude Code's transcripts, and the scan says so.
mcp__cursor__<tool>, because Cursor's hook payload does not say which server the tool
belongs to. A session file does name the server, so scan evaluates the same call as
mcp__<server>__<tool>. A guardrail that names a server can therefore match in scan
and not in the live hook; any other guardrail gets the same verdict in both.It reads local files and nothing else. No account, no upload, and scan itself makes
no network call; the one exception anywhere in this tool is crash reporting, which is
off unless you turn it on, and which scan never touches. To publish a report from
Claude Code, see Share a report from Claude Code.
You get two things: a summary in the terminal (the counts, the matches by severity and
by action, the most serious findings and the top repeats) and one self-contained
agenttrail-guard-report.html in the directory you ran it from, or wherever --out
points. The report opens in your browser when it is written; --no-open skips that, and
it stays closed when CI is set. That file is a single document with its styles and the
AgentTrail logo inline, and it follows your system's light or dark theme. It loads
nothing from outside itself, so it opens from a file:// URL with the wifi off and
cannot phone home when someone else opens it. The source repository's address in its
footer is text. A Claude Code report also carries one short note about AgentTrail with a
single link to www.agenttrail.sh, which does nothing until someone clicks it; a Cursor
report carries none.
It is built to be shared, so it is redacted before it is written. Every command goes
through the secret scrubber, then a path redactor, then an identifier redactor, and every
MCP tool call has its payload values structurally redacted: <path> stands for a
filesystem path or URL, <name> / <host> / <user> / <message> / <comment> for an
identifying operand of a command, <value> for a redacted MCP payload value, and
[REDACTED:…] for a secret. A file-tool call (Edit, Write, Read and the like) is shown as
the tool and the kind of file the guardrail that matched it looks for, such as
Edit <test config> or Write <agent memory file>; only a short list of well-known names
that identify no one is kept as it is, such as vitest.config.ts, package.json,
CLAUDE.md, MEMORY.md, .env.local and .github/workflows/<file>.yml. Working
directories and file paths appear nowhere in it, and
the projects your sessions came from are a count and never names, because the install
is per-machine and your projects are your business.
Identifying names are a weaker claim than paths, and the difference matters. A path
is structural (a token either has a separator or it does not), so redacting one is a
total rule. A name is not: acme-prod-db is a container to a human and an ordinary word
to a regex. For shell commands the identifier redactor closes that by knowing a fixed
list of tools, plus a few shapes it redacts in any command; both are listed under
What the guard does not do. That is a deny-list, and it will
miss the tool nobody thought of. An mcp__… tool call is redacted the other way
round: its payload is a structured value, so rather than deny-listing known-sensitive
fields, every value is redacted to <value> by default and only the field names and the
shape are kept; no raw MCP payload value reaches the file. Redaction is thorough but
not a guarantee. Run scan --review to read every line the file will contain (and the
raw MCP payload values behind each <value>) before it is written, and read it before
you post it anywhere public.
No dollar figures. Counts and token totals are exact and come straight off the transcript; the report carries no risk-in-dollars number, not even one labelled "estimated", because that is a count multiplied by an assumption.
scan --agent <claude|cursor|codex> required: whose sessions to read
scan --dir <root> read from somewhere other than ~/.claude/projects or ~/.cursor/projects
scan --out <file> write the report to this file (or into this directory)
scan --no-open do not open the report in your browser
scan --artifact write the report as page content only, for publishing as a Claude artifact
scan --review print every line the report will contain, and ask before writing it
scan --json print the result as JSON and write no file
In Claude Code, run /agenttrail-guard:share-report, or ask Claude to share your guard
scan report. The plugin's skill runs the same scan and writes the report into a temporary
folder, not your project. It shows you the summary and the report's redaction notes, and
can list every command shape and guardrail name the file contains. It does not use
--review, whose output includes raw MCP payload values the file never carries, so those
values stay out of the conversation. Then it asks you, and only on a yes does Claude Code
publish that file, unchanged, as a Claude artifact.
The artifact is a page on claude.ai under your own account, visible only to you until you
use Share on it. On Team and Enterprise plans it also follows your organization's
artifact settings. The upload is done by Claude Code's Artifact tool, not by the guard;
scan uploads nothing. The scan's summary appears in your Claude Code conversation like
any other command output, and the report covers every Claude Code project on this
machine.
Artifacts need a claude.ai sign-in on a Pro, Max, Team or Enterprise plan. Signed in with
an API key, on Bedrock, Vertex AI or Foundry, or with artifacts turned off, the skill
gives you the path to the local file instead. Cursor scans are not published this way:
scan --agent cursor writes the report and opens it in your browser. The skill ships
with plugin 0.3.0; to get it, run agenttrail-guard init --agent claude.
Off by default, opt-in, and stack traces only. A crash is recorded to a file on your
machine either way; nothing is transmitted until you turn reporting on and run
agenttrail-guard crash-report --send yourself. Stack traces are scrubbed for secrets
and for file paths before they go anywhere: a raw stack carries your username, your
project's name and your client's name. No commands, no file contents, no environment
variables, ever.
agenttrail-guard crash-report # what is on, and what is waiting
agenttrail-guard crash-report --enable # opt in
agenttrail-guard crash-report --send # transmit, only if you enabled it
agenttrail-guard crash-report --clear # delete everything spooled
--send needs one more thing besides the setting: an endpoint, and none ships by
default. Bring your own with --endpoint <url>, the AGENTTRAIL_GUARD_CRASH_ENDPOINT
environment variable, or "crashEndpoint" in config.json. With nothing configured,
--send refuses by name and transmits nothing, so a default install has no endpoint to
send to, by design. crash-report (no flag) shows whether one is configured.
Three properties hold on every single invocation, and each has a test that fails the build if it stops holding:
It always exits 0, and can never exit 2. Exit 2 is Claude Code's blocking
signal and overrides the JSON decision, including allow. In Cursor and in Codex,
exit 2 blocks the call as a deny would. The hook bundle contains no process.exit
at all, so this is structural rather than a pattern someone remembered to grep for.
It writes at most one JSON object to stdout and nothing else.
{ and end } is
discarded as plain text and the tool call proceeds. One stray log line would make
the guard decorative.{"permission":"deny",…}
blocks, {"permission":"ask",…} asks (at beforeShellExecution only), and {} is
no opinion, the answer to a warning and to a call nothing matches.It fails open, and never answers allow.
allow skips Claude Code's own permission prompt,
so the guard leaves every call it does not block or hold to Claude Code's normal
permission flow. A parse error or internal throw gives no decision, only a message
that the call was not checked.{}, and the call goes
through Cursor's own approval as if the guard were not installed. A parse error or
internal throw answers {} and writes a crash record, which status counts. The
guard's entries do not set failClosed, so a hook that cannot run, or runs past its
timeout, lets the call through.allow, which would skip Codex's own approval
card, and never asks, because Codex rejects an answer that asks and runs the action;
so a guardrail that would hold a call blocks it there instead. Where it has no
opinion it writes nothing. Codex itself fails open on a crash, a non-zero exit, bad
output and a timeout, and has no setting that changes that.If our code has a bug, your command goes through the app's normal permission flow.
Local settings, logs, and crash records live under ~/.agenttrail/guard/.
Guard writes its private files at mode 0600 where supported. It also keeps
cursor/ and codex/ integration folders there and adds entries to each app's hooks
file when you install that integration. The
guard writes nothing else anywhere (not in your project, not in your Claude Code
settings, not in Codex's config.toml), with exactly one exception: scan writes
agenttrail-guard-report.html into the directory you run it from, or to the path you
give --out, because that file is for you to read and share.
config.json: the packs you turned off (disabledPacks), the guardrails you turned off
one at a time, per-guardrail action overrides, and the per-guardrail allowlist. It records
only what you changed: every pack not named in disabledPacks is on, including packs
added in later releases. Written by init if it is absent, and never overwritten after
that.
config.json also holds productLine. Set it to false to turn off
the line about AgentTrail OS.
.product-line: when each command last printed that line, so it shows at most once a
day. Deleting it is safe.
guardrails.json: your own guardrails, enforced alongside the bundled ones. Starts as
[]; guardrails add writes to it. One here that is invalid is skipped, not fatal,
and status is where you find out: a hook's stderr goes to a debug log you will
never read, so that is the only place it can tell you. A file that is corrupt
entirely is ignored, and the bundled guardrails keep working.
events.jsonl: the guard's own record of what it decided. One line per tool call
that matched a guardrail: the time, the tool, the decision, the guardrail id, the
command, and last agent, the app that sent the call (claude, cursor or codex).
A Cursor terminal command passes two checkpoints and is still written once, and so
does a Codex action that reaches both of the guard's Codex entries. It is what lets
status tell you which guardrail is the noisy one.
We say we send you nothing, and separately we keep a file listing commands your
assistant ran. Both are true, so here is exactly what that file is. It never
leaves your machine: nothing in the guard transmits it, and there is no code path
that could. Every command in it is scrubbed before it is written, not on the way
out. It does not grow forever: it is capped at 1 MiB, and older records are
dropped past 30 days. You can empty it whenever you like with
agenttrail-guard status --clear-history, and deleting the file outright is always
safe; the guard simply starts a new one.
crashes/: crash reports from the hook and scan, written whether or not crash
reporting is on. Scrubbed stack traces only, capped at 20 files and 30 days. Nothing
here is transmitted unless you turn crash reporting on and run
yourself, and deleting the directory is always safe.
In ~/.cursor/hooks.json, which belongs to Cursor, the guard adds one entry under
preToolUse and one under beforeShellExecution, and keeps every other key and entry.
The file keeps its mode, and a file the guard creates gets 0600. A project's
.cursor/hooks.json and enterprise hooks files are never touched.
In ~/.codex/hooks.json, which belongs to Codex, the guard appends one entry for the
pre-tool event and one for the permission-request event, and keeps every other key and
entry where it was. The file keeps its mode, and a file the guard creates gets 0600.
A project's .codex/hooks.json, an enterprise-managed hooks file, and Codex's
~/.codex/config.toml are never touched; the last of those matters, because Codex
adds the two hook sources together and an entry in both would run the guard twice on
every call.
uninstall, for any of the three apps, deliberately leaves config.json,
guardrails.json, events.jsonl and crashes/ in place. They are your settings, and
finding them silently gone after a reinstall would be worse than finding them there.
uninstall --agent cursor removes the guard's entries from ~/.cursor/hooks.json and
deletes the files in cursor/; uninstall --agent codex does the same for
~/.codex/hooks.json and codex/.
AgentTrail Guard is licensed under the Apache License 2.0, commercial use included. If you rely on Guard, a star helps other developers find it.
Made with ❤️ by agenttrail.sh
--scrub has been tested only on synthetic
session files built from Codex's documented rollout format, never on a real Codex
session, and the command says so each time it runs.initsettings.json"Bash(git:*)"scan report and a crash report all go through the scrubber), but
a command that contains a secret and is allowed still runs exactly as the agent
wrote it.events.jsonl is
scrubbed first: AWS keys, GitHub and Slack tokens, JWTs, private keys, connection
strings, .env-style assignments, emails, and card numbers all become
[REDACTED:…]. What it does not catch is a secret passed as a bare command-line
argument, with no = or : between the name and the value: the patterns are
anchored to an assignment, so --token abc123 reads as two ordinary words. Treat
the log as scrubbed, not sanitized. (The scan report goes further and does
redact a secret handed to a long flag such as --token, --password, --key or
--vendor-key, a value after a name ending in _KEY or -key, an Authorization
header, and any long random-looking string; the decision log does not.)scan report's name redaction is a deny-list for shell commands and an
allow-list for MCP. Paths are redacted structurally, so that rule is total. Shell
names are not; the identifier redactor knows a fixed list of tools: container
runtimes, kubectl (resource names included), the database clients (host, database,
and the SQL passed to -c / -e), ssh, git commit messages and identity
settings, 1Password's op (item and vault names), and gh titles and bodies; and it
also redacts, in any command, a secret handed as a flag value (--token,
--password, --key), a UUID, a heredoc body (which becomes <message>), and the text
of a # comment. Cloud account identifiers are redacted where a name or flag gives
them away: CLOUDFLARE_ACCOUNT_ID and --account-id, any 32-hex operand of a
wrangler command, an AWS account number after --account or inside an ARN,
--project for gcloud, gsutil, bq and firebase, and the login in
ssh -o User=. A bare
operand naming a resource for a tool not on the list
survives, and a branch or tag given as a plain git operand (git checkout my-branch,
git tag -a v1) is kept readable on purpose, as is a bare folder name (cd my-app,
git -C my-app). A glued short-flag password
(mysql -pSECRET) is not caught: only long secret flags and .env-style assignments
are. An mcp__… payload is handled the opposite way: every value is redacted to
<value> by default and only the field names are kept, so no raw payload value reaches
the file.scan report is caught by format, by name and by shape, and the last two
are guesses. After the known formats, a value that follows a name ending in _KEY or
-key, a raw Authorization header, and any run of 32 or more letters, digits, - and
_ that looks random is masked, the last as [REDACTED:secret:generic]. A secret
shorter than 32 characters with no revealing name, a secret that is only hexadecimal
(a hex key looks like a commit hash or a digest, so hex is masked only under a name or
flag), a secret split by + / ., an unnamed secret followed by a ., an unnamed secret
that ends in =, and an unnamed lower-case key written as short dashed groups (four or
five characters each) can survive; a run that spells out a pod or release name (three or
more deployment words such as prod, canary or west, every segment short) is left
readable. Redaction is best effort, not a guarantee: read the report, or run
scan --review, before you share it.guardrails.json it is yours,
and nothing redacts it: a title reading "AcmeCorp internal audit" appears in the report
verbatim. Paths and secrets pasted into a title are taken out (the title goes
through the same three passes as a command), but a name you chose is not something a
redactor can distinguish from a word. Run scan --review to read every line, commands
and guardrail names both, before the file is written.status --clear-history.
The bound that always holds is the size one.events.jsonlstatusstatus, events.jsonl
and scan are where each decision is recorded exactly.~/.cursor/hooks.json.codex execevents.jsonlstatusmcp__server__tool name Claude Code uses, taken from Codex's own source. No MCP
server was configured on the machine these measurements come from, so that path is
reasoned rather than observed.Bash, an edit through apply_patch, and an MCP call named
mcp__server__tool. Anything else yields no verdict and no line in the decision
log, so it is neither checked nor recorded. This is not hypothetical: Codex Desktop
reports its shell tool as shell_command, which means the guard would not check
commands there at all.~/.codex/hooks.json and the guard is not in the loop at all.allow_managed_hooks_only = true set in a managed configuration, Codex runs only the
hooks that configuration provides and skips yours, the guard's included. There is no
message about it; the guard simply never fires.crash-report --sendcursor/: only after init --agent cursor:
guard-hook.mjs, the copy of the hook that Cursor runs;hooks.json.backup, your ~/.cursor/hooks.json as it was before the first install,
or an empty hooks.json.was-absent when there was no such file;install.json, which records when the install was made, the hook copy, the Node it
runs and the guard version, so status can tell when a different version made it.Running init --agent cursor again when nothing would change writes nothing.
codex/: only after init --agent codex, and deliberately the same four files as
cursor/, so uninstall and status have one shape to handle:
guard-hook.mjs, the copy of the hook that Codex runs;hooks.json.backup, your ~/.codex/hooks.json as it was before the first install,
or an empty hooks.json.was-absent when there was no such file;install.json, which records when the install was made, the hook copy, the Node it
runs and the guard version, and, because Codex identifies an approval by an entry's
position, where in the file the guard's entries were written, so status can tell
you when something has moved them.Running init --agent codex again when nothing would change writes nothing. Nothing
in here is the approval itself: that lives in Codex's own config.toml, which the
guard neither reads nor writes.