Guard - xsyetopz/claudetto GitHub Wiki
This page tells how the guard slice of the core plugin claudetto decides which calls run, which ask a person, and which stop.
A user reads it to learn why Claude Code shows a dialog or a refusal.
A contributor reads it before an edit of slices/guard/.
The settings snippet has no allow list and no ask list.
permissions.defaultMode is default, and permissions.disableAutoMode is disable, so the classifier of auto mode is off.
The slice replaces the lists with one gate that reads the command.
The gate lets a call run unless the call can lose work for good, harm the system, or read a secret.
A rule of the settings cannot read a command, so it cannot tell rm -rf node_modules from rm -rf src.
The gate can.
The slice adds one tool.check hook on the tools Bash, Read, Grep, Glob, Edit, and Write.
It gives each call one of three answers:
| Answer | What happens |
|---|---|
| Allow | The call runs. This is the default |
ask |
A person decides in the dialog, unless Jev decides first (see below) |
deny |
The call stops, and Claude gets the reason |
The hook keeps a deny of the engine, such as a deny rule of the user.
The slice reads the shell syntax and does not run the shell.
It finds a command in $(...), bash -c, a group, and a branch of if.
The gate denies these calls:
-
sudo,doas, andsu. - A command that can erase a disk or turn off a protection of the system:
mkfs,newfs,ddwithof=/dev/,diskutilwitherase,zero,secureErase,partition, orreformat,csrutil(exceptstatus),spctl --master-disableand--global-disable,nvramthat sets or deletes a value, andsecurity delete-*. - A recursive
chmod,chown, orchgrpon/or the home folder. - A write to
/System,/Library, or/usr(but not/usr/local), by a command, by a redirect, or byEditorWrite. - A read of a secret: a
.envfile (but not.env.example, a sample, or a template),~/.ssh,~/.gnupg,~/.aws/credentials,~/.netrc, and~/.docker/config.json. This coversRead,Grep, and the globs ofGlob. - A delete of
/or the home folder.
The reason of a deny of a Bash command, except a read of a secret, ends with If the user wants this command, they can run `! <cmd>`.
The commands rm, shred, srm, unlink, and find with -delete or -exec rm are deletes.
- A delete in the temporary folder runs, but a delete in a project in the temporary folder gets the check of the project.
- A delete of a build folder runs:
node_modules,dist,build,out,target,coverage,.next,.nuxt,.turbo,.cache,.parcel-cache,.build,DerivedData,__pycache__,.pytest_cache,.mypy_cache,.ruff_cache,.venv, and.gradle. - A delete outside the project asks.
- A delete inside the project runs
git statusandgit ls-filesfor the paths. The delete runs when each path has a commit and no change. A path with a change, with no commit, or in a folder thatgitdoes not manage asks. For a delete, ignored and untracked files count as changes, because the delete removes them. - A delete whose paths come from a pipe (
xargsandparallel), or whose target the gate cannot read, asks.
A git command that can lose local work asks when the paths have a change:
reset --hard, checkout with a force flag or paths, restore, clean -f, stash drop, and stash clear.
clean -f counts untracked files, and clean -fx and clean -fX count ignored files too.
reflog expire and gc --prune=now or --prune=all always ask.
A git push with --force, --force-with-lease, --delete, --mirror, or a refspec that starts with + or : asks.
This ask is for a person only, because Jev does not decide it.
These ask, because the gate cannot know what they do:
-
eval. - A shell script that the gate cannot read, or a script file with no commit.
- A command that is too long to read (
SHELL_SCAN_MAX_CHARS).
Each ask carries decider: "person", so the dialog has no "always allow" row, and no classifier settles it.
Where no person can be asked, Claude Code refuses the call.
An ask of a delete or of local git work goes to Jev first when both of these are true:
- The
decideoption of the plugin isjev, which is the default. -
TYPESAFE_API_KEYis set.
The gate runs claudetto-decide yes --json once, with a timeout of JEV_TIMEOUT_MS (12,000 ms).
The question is whether each file that the command can lose is safe to lose, such as build output, a cache, a log, or a temporary file.
The command and the reason of the ask go with it.
The call runs when P(yes) is JEV_YES_MIN (0.8) or more.
Any other answer, a failure, or a timeout leaves the ask to a person.
See Decide for the Jev client.
The ask for a push, for eval, and for a script is for a person only.
The gate writes each ask as one JSON line to <tmp>/claudetto/gate/<sessionId>.jsonl.
The fields are at, tool, target, reason, and decider, which is jev or person.
A log that cannot be written changes no answer.
The session cleanup removes the log with the other files of the session (Session Hygiene).
Claude Code skips a hook that throws or times out, so the call would run.
The slice adds an error handler for this case.
If the hook fails, the handler gives an ask for a person, with the reason that the gate failed.
A git run that fails or takes more than GIT_TIMEOUT_MS (5,000 ms) counts as a change, so a person decides.
- A path that a variable, an alias, or a script hides from the shell reader. Only the cases above ask.
- A
Bashedit of a file that is not a delete. The edit ledger of Session Hygiene tells Claude about it when a file changes. - The
denylist of the settings snippet. It is a second layer for the same dangers, and it holds in each mode (Settings Snippet).