On this page
How claudewheel guardrails work: the 4 enforcement tiers, subagent-versus-main-agent handling, command-string caveats, upgrading existing profiles, and the tools stripped from every session.
#Guardrails
claudewheel ships a canonical set of command guardrails that every profile inherits. The guardrails discourage or block destructive shell commands (bulk git add, rm, history rewrites, branch deletion) and steer agents toward safe alternatives such as safegit and saferm. The rules live in one place and drive both the deployed hook scripts and each profile's permission arrays.
#Enforcement tiers
Every rule belongs to exactly one of 4 tiers. The tier decides where the rule is enforced (a PreToolUse/PostToolUse hook, the settings deny/ask arrays, or both) and who it applies to. The hook is always the authoritative enforcer when a rule has one; the settings arrays are best-effort defense-in-depth for the plain command form.
- HARD_DENY -- denied for everyone, both the main agent and subagents, via
a PreToolUse hook. A backing settings deny glob may exist as defense-in-depth, but it never reproduces the hook's full match surface (compound commands, sudo/env/xargs/find -exec wrappers, alternate remotes). Some HARD_DENY rules own no deny glob at all.
- ESCALATE -- denied only when a subagent attempts the command. The main
agent falls through the hook silently so the settings ask rule prompts the user to approve it deliberately.
- ADVISE -- the command runs, then a
PostToolUsehook nudges the agent
with advice via additionalContext. There are no settings entries and nothing is blocked.
- ASK -- a pure settings
askrule with no hook involvement. The user is
prompted before the command runs.
#Subagents versus the main agent
The blocker hook distinguishes a subagent from the main agent using the agent_id field in the PreToolUse payload. Claude Code populates agent_id only for subagent tool calls, so a non-empty agent_id marks a subagent. HARD_DENY rules block regardless of agent_id, while ESCALATE rules block only when agent_id is set and otherwise let the main agent through.
This is why an ESCALATE command like git push is refused outright for a subagent (with a message telling it to report to its parent) but merely prompts the user when the main agent runs it. The distinction keeps risky, outward-facing actions in the hands of the human-supervised main agent.
#Command-string caveat
The hooks match against the raw command string with grep -qE, anchored to the start of a shell segment. They do not parse the shell. A command that only mentions a guarded token -- for example inside an echo, a grep pattern, a comment, or a heredoc -- can still trip the matcher and be nudged or blocked even though nothing dangerous would actually run.
This is a deliberate trade-off: false positives are safe (you rephrase or split the command), whereas parsing the shell to eliminate them would be far more fragile than a conservative string match. When a benign command is blocked, move the guarded token out of the command line or run the pieces separately.
#Upgrading existing profiles
The guardrail model evolves between releases. Existing profiles keep whatever rules were current when they were created, so after upgrading claudewheel you should re-apply the canonical model to bring older profiles up to date:
- Run
claudewheel reconcile-permissionsto rewrite each profile's
deny/ask/allow permission arrays to match the current model.
- Run
claudewheel patch-profilesto sync the deployed hook scripts and
disallowedTools defaults into every profile and shared-settings.json.
Both commands support --dry-run so you can preview the changes before writing anything to disk, and you should: the reconciliation is exact, so it prunes any permission rule, hook entry or disallowedTools entry you added by hand, and nothing is backed up.
Because of that pruning both commands are declared consequential: the CLI framework asks Proceed? [y/N] before writing, and when there is no terminal to answer at it refuses with error: stdin is not interactive; pass --approve-consequential to confirm. A script or hook that means to reconcile passes --approve-consequential. --dry-run is never gated.
#Rule reference
The table below is generated directly from the canonical rule set, so it always reflects the guardrails shipped in this version. "Settings coverage" reports how completely a rule's deny/ask glob(s) track its hook surface as 1 of 3 levels (FULL, PARTIAL, or NONE), or n/a for tiers with no settings backstop.
| Key | Tier | Settings coverage | Advice |
|---|---|---|---|
rm | HARD_DENY | PARTIAL | Use 'saferm delete --description "why" file1 file2' instead of 'rm'. |
git-add-bulk | HARD_DENY | PARTIAL | Use 'safegit commit -m "msg" -- file1 file2' instead of 'git add'. |
git-stash | HARD_DENY | FULL | Never 'git stash'. Commit the work in progress on the current branch with 'safegit commit' instead. |
git-restore | HARD_DENY | FULL | Use the Edit tool to revert specific lines instead of 'git restore'. |
git-checkout-file | HARD_DENY | NONE | Use the Edit tool to revert specific lines instead of 'git checkout -- file'. |
git-checkout | HARD_DENY | FULL | 'git checkout' is deprecated here; use 'git switch' for branches (plain git switch is allowed) or the Edit tool to revert files. |
git-push-delete | HARD_DENY | PARTIAL | Deleting remote branches is destructive; ask the user to do this deliberately. |
push | ESCALATE | FULL | Pushes happen only via rlsbl release run. |
git-reset | ESCALATE | PARTIAL | git reset is destructive in shared worktrees. |
git-switch-force | ESCALATE | FULL | Forced switch destroys uncommitted work in shared worktrees. |
gh-workflow-run | ESCALATE | FULL | Triggering CI workflows is an outward-facing action. |
saferm-purge | ESCALATE | FULL | saferm purge permanently destroys archived files. |
git-rebase | ESCALATE | PARTIAL | Rebase rewrites history in shared worktrees. |
safegit-author-rewrite | ESCALATE | FULL | Author rewriting is history rewriting. |
kill | ADVISE | n/a | This kill/pkill ran, but prefer building graceful stop commands or PID-file-based stop scripts into your tooling instead of killing processes directly. |
sudo | ASK | n/a | Prompted via the settings ask rule (no hook). |
#Stripped tools
Beyond hooks and permission rules, claudewheel removes a set of Claude Code's tools from every session it launches, via the --disallowedTools launch argv. The operating principle is that less is more: every exposed tool is an invitation for the agent to stray into it unnoticed during a long unattended run, and the fewer tools the harness exposes, the more intelligently the model calls the ones that remain.
The table below is generated directly from the canonical model, so it always reflects the strip list shipped in this version.
| Tool | Why |
|---|---|
Artifact | Artifacts are unwanted; when an HTML report is wanted, it will be asked for explicitly. |
DesignSync | Serves Claude Design, which is unwanted -- and it only works with short-term logins, never the long-lived OAuth tokens claudewheel prefers. |
EnterPlanMode | Plan mode hijacks the session lifecycle: accepting a plan clears the session and makes the previous messages unreachable in the TUI. Fresh context for implementation is better achieved deliberately -- a new session or a subagent orchestrator. |
EnterWorktree | Exposing worktrees as tools invites silent, unauthorized use: work strays into a temp worktree, later sessions cannot find it, tokens are wasted rebuilding it, and stale files linger. Bash covers the rare legitimate case, explicitly. |
ExitPlanMode | Counterpart of EnterPlanMode; banned with it. |
ExitWorktree | Counterpart of EnterWorktree; banned with it. |
LSP | Injects compile-time diagnostics mid-work that are stale by the time the agent finishes; real errors surface at build time anyway. A net distraction left over from the era of slow human typing. |
NotebookEdit | No Jupyter notebooks here -- and their non-plaintext format is a reason to avoid them entirely. Plain file writes cover everything. |
PushNotification | Belongs to Remote Control, which is rejected wholesale. When a Remote Control pairing exists it also pushes model-authored text to phone and email with no permission prompt. |
RemoteTrigger | Client for claude.ai routines: autonomous cloud agents acting as the user with no in-run approvals, self-approving locally. Stays banned even while dormant behind a server-side feature flag, as insurance against the flag flipping. |
ReportFindings | Exists solely to serve /code-review, which is unwanted; inert in terminal sessions regardless. |
Skill | Bloatware: injected prompt payloads. Instructions worth having live in the repository. |
TaskCreate | The task-tracking system is dead weight: a months-long usage survey found this was the only family member ever used (thousands of calls) while the conversation itself served as the real task history -- so even the one used tool goes. |
TaskGet | Task-tracking family: never used once over months of active work; the conversation is the task history. |
TaskList | Task-tracking family: never used once over months of active work; the conversation is the task history. |
TaskOutput | Task-tracking family: never used once over months of active work; the conversation is the task history. |
TaskStop | Task-tracking family: never used once over months of active work; the conversation is the task history. |
TaskUpdate | Task-tracking family: never used once over months of active work; the conversation is the task history. |
The list is a declaration, not a measurement: a name stays banned even while the installed Claude Code version happens not to offer that tool (such an entry is dormant insurance, not an error). For live numbers against the installed binary -- baseline versus stripped tool counts, and which entries are currently inert -- run scripts/tool-strip-report.