Agent Permissions (How To)
June 10, 2026 · View on GitHub
Agent permission rules are managed as layered chezmoi data, then rendered into several editors' configs.
Source files:
home/.chezmoidata/agent-permissions/*.yaml(overlays merged lexically)- schema:
schemas/agent-permissions.schema.json
Shared prep partial (collects the top-level rules + condition-enabled ruleSets once; see CONTEXT.md and ADR-0001):
home/.chezmoitemplates/agent-permission-rules.tmpl— emits the collected rule list as JSON
Render targets (each consumes the prep partial, keeps its own per-rule conditions gate + shaping):
- OpenCode via
home/dot_config/opencode/modify_opencode.json(partialhome/.chezmoitemplates/opencode-permission.tmpl) — honours allkinds - Cursor via
home/Library/Application Support/Cursor/User/settings.json.tmplandhome/AppData/Roaming/Cursor/User/settings.json.tmpl(partialhome/.chezmoitemplates/cursor-terminal-auto-approve.tmpl) —bashkind only - Zed via
home/dot_config/zed/modify_settings.jsonandhome/AppData/Roaming/Zed/modify_settings.json(partialhome/.chezmoitemplates/zed-terminal-tool-permissions.tmpl) —bashkind only
A kind renders to a target only when agentPermissions.kinds.<kind>.targets.<target> is
truthy (e.g. bash enables opencode and zed; external_directory and permission_key
enable opencode only).
Data shape
Each overlay contributes to:
agentPermissions:
kinds:
bash:
destinationType: namespaced
destinationKey: bash
targets:
opencode: true
zed: true
supportsMatchMode: true
defaultMatchMode: exact
wildcardSuffix: " *"
external_directory:
destinationType: namespaced
destinationKey: external_directory
targets:
opencode: true
permission_key:
destinationType: top_level
targets:
opencode: true
rules:
- kind: bash
pattern: "git status"
op: allow
bashMatchMode: exactAndWithArgs
All rule types share the same schema. Kind routing is data-driven via agentPermissions.kinds.
Kind configuration
agentPermissions.kinds controls where each rule kind is rendered:
destinationType: namespaced-> rule emits intopermission.<destinationKey>destinationType: top_level-> rule emits at top level ofpermissiontargets.<target>-> per-target enablement for the kind (truthy = rendered to that target)supportsMatchMode+defaultMatchMode+wildcardSuffixcontrol wildcard expansion behavior for that kind
Note: the per-rule conditions gate is applied inside each render target, not in the shared
prep partial — OpenCode uses a strict gate (an absent condition key excludes the rule) while
Cursor/Zed use a loose gate (an absent key includes it). Both agree for the conditions used in
this repo (ci, private), which are always present.
Rules then reference a configured kind by kind.
Rule fields
Supported rule fields:
kind(required): key fromagentPermissions.kindspattern(required): match stringop(required):allow,ask, ordenyconditions(optional): key/value match against template data (for exampleprivate: false)bashMatchMode(optional,kind: bashonly):exact,withArgs, orexactAndWithArgs
Rule kinds
bash
Generates entries inside "permission"."bash".
bashMatchMode controls exact and wildcard emission:
exact->"pattern"withArgs->"pattern *"exactAndWithArgs-> both keys
This exists because OpenCode glob matching does not treat "cmd *" as equivalent to bare "cmd" for all cases.
external_directory
Generates entries inside "permission"."external_directory":
- kind: external_directory
pattern: "~/.local/share/chezmoi/**"
op: allow
permission_key
Generates top-level permission keys (same level as bash/external_directory), useful for MCP tool patterns:
- kind: permission_key
pattern: "atlassian_*"
op: ask
conditions:
private: false
Excluded agents
Hermes Agent
Hermes is intentionally excluded from this permission system. Hermes' approval model
is detection-based, not policy-based: a hardcoded DANGEROUS_PATTERNS regex list
flags risky commands at runtime and prompts the user; a hardcoded hardline floor
unconditionally blocks catastrophic commands (rm -rf /, mkfs, shutdown, etc.).
The only configurable surface is command_allowlist in ~/.hermes/config.yaml,
which pre-approves commands that already matched a dangerous pattern so the user
isn't prompted again. Commands that don't match any dangerous pattern run without
approval — so pre-seeding our allow rules (which are almost entirely non-dangerous
commands like ls, cat, git status) into the allowlist would be a no-op.
Overlay strategy
Use the same split pattern as Brew and MCP:
00-base.yaml: shared defaults10-<profile>.yaml: personal/work/fork-specific rules
In this repo, Atlassian-specific permission keys live in home/.chezmoidata/agent-permissions/10-chipwolf.yaml.
Validation
- Render each target (
chezmoi cat <target-path>orchezmoi execute-template) and confirm expected permission keys are present/absent. - After changing the shared prep partial, confirm render output is byte-identical for the
OpenCode, Cursor, and Zed settings targets (
chezmoi catbefore/after). - Run test suite (
tests/source/chezmoi.bats). - Run
pre-commit run --all-filesbefore commit.
See .agents/skills/update-agent-permissions/SKILL.md for the full validation workflow.