Guard - xsyetopz/claudetto GitHub Wiki

Guard

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/.

Why It Exists

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.

What the Hook Does

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.

What the Gate Denies

The gate denies these calls:

  • sudo, doas, and su.
  • A command that can erase a disk or turn off a protection of the system: mkfs, newfs, dd with of=/dev/, diskutil with erase, zero, secureErase, partition, or reformat, csrutil (except status), spctl --master-disable and --global-disable, nvram that sets or deletes a value, and security delete-*.
  • A recursive chmod, chown, or chgrp on / or the home folder.
  • A write to /System, /Library, or /usr (but not /usr/local), by a command, by a redirect, or by Edit or Write.
  • A read of a secret: a .env file (but not .env.example, a sample, or a template), ~/.ssh, ~/.gnupg, ~/.aws/credentials, ~/.netrc, and ~/.docker/config.json. This covers Read, Grep, and the globs of Glob.
  • 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>`.

What the Gate Asks

Deletes

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 status and git ls-files for 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 that git does 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 (xargs and parallel), or whose target the gate cannot read, asks.

Local git Work

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.

History on a Remote

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.

Commands the Gate Cannot Read

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).

Who Decides an ask

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 decide option of the plugin is jev, which is the default.
  • TYPESAFE_API_KEY is 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 Log

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).

Fail Closed

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.

What the Gate Does Not Cover

  • A path that a variable, an alias, or a script hides from the shell reader. Only the cases above ask.
  • A Bash edit of a file that is not a delete. The edit ledger of Session Hygiene tells Claude about it when a file changes.
  • The deny list of the settings snippet. It is a second layer for the same dangers, and it holds in each mode (Settings Snippet).

Related Pages

⚠️ **GitHub.com Fallback** ⚠️