Skip to content
Guardrails
Edit
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 PostToolUse hook nudges the agent

with advice via additionalContext. There are no settings entries and nothing is blocked.

  • ASK -- a pure settings ask rule 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-permissions to rewrite each profile's

deny/ask/allow permission arrays to match the current model.

  • Run claudewheel patch-profiles to 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.

Rule reference
KeyTierSettings coverageAdvice
rmHARD_DENYPARTIALUse 'saferm delete --description "why" file1 file2' instead of 'rm'.
git-add-bulkHARD_DENYPARTIALUse 'safegit commit -m "msg" -- file1 file2' instead of 'git add'.
git-stashHARD_DENYFULLNever 'git stash'. Commit the work in progress on the current branch with 'safegit commit' instead.
git-restoreHARD_DENYFULLUse the Edit tool to revert specific lines instead of 'git restore'.
git-checkout-fileHARD_DENYNONEUse the Edit tool to revert specific lines instead of 'git checkout -- file'.
git-checkoutHARD_DENYFULL'git checkout' is deprecated here; use 'git switch' for branches (plain git switch is allowed) or the Edit tool to revert files.
git-push-deleteHARD_DENYPARTIALDeleting remote branches is destructive; ask the user to do this deliberately.
pushESCALATEFULLPushes happen only via rlsbl release run.
git-resetESCALATEPARTIALgit reset is destructive in shared worktrees.
git-switch-forceESCALATEFULLForced switch destroys uncommitted work in shared worktrees.
gh-workflow-runESCALATEFULLTriggering CI workflows is an outward-facing action.
saferm-purgeESCALATEFULLsaferm purge permanently destroys archived files.
git-rebaseESCALATEPARTIALRebase rewrites history in shared worktrees.
safegit-author-rewriteESCALATEFULLAuthor rewriting is history rewriting.
killADVISEn/aThis kill/pkill ran, but prefer building graceful stop commands or PID-file-based stop scripts into your tooling instead of killing processes directly.
sudoASKn/aPrompted 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.

Stripped tools
ToolWhy
ArtifactArtifacts are unwanted; when an HTML report is wanted, it will be asked for explicitly.
DesignSyncServes Claude Design, which is unwanted -- and it only works with short-term logins, never the long-lived OAuth tokens claudewheel prefers.
EnterPlanModePlan 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.
EnterWorktreeExposing 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.
ExitPlanModeCounterpart of EnterPlanMode; banned with it.
ExitWorktreeCounterpart of EnterWorktree; banned with it.
LSPInjects 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.
NotebookEditNo Jupyter notebooks here -- and their non-plaintext format is a reason to avoid them entirely. Plain file writes cover everything.
PushNotificationBelongs 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.
RemoteTriggerClient 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.
ReportFindingsExists solely to serve /code-review, which is unwanted; inert in terminal sessions regardless.
SkillBloatware: injected prompt payloads. Instructions worth having live in the repository.
TaskCreateThe 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.
TaskGetTask-tracking family: never used once over months of active work; the conversation is the task history.
TaskListTask-tracking family: never used once over months of active work; the conversation is the task history.
TaskOutputTask-tracking family: never used once over months of active work; the conversation is the task history.
TaskStopTask-tracking family: never used once over months of active work; the conversation is the task history.
TaskUpdateTask-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.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search