Skip to content

Permissions

Permissions control what an agent can do within its session. Herdctl provides fine-grained control over tool access and permission approval modes using a flat configuration structure that maps directly to the Claude Agents SDK. This allows you to create agents with appropriate access levels—from read-only support bots to full-access development agents.

agents/my-agent.yaml
permission_mode: acceptEdits
allowed_tools:
- Read
- Write
- Edit
- "Bash(git *)"
- "Bash(npm *)"
- "Bash(pnpm *)"
denied_tools:
- WebSearch
- "Bash(rm -rf *)"
- "Bash(sudo *)"

The permission_mode field controls how Claude Code handles permission requests. This maps directly to the Claude Agents SDK’s permission modes.

permission_mode: acceptEdits # default
ModeDescriptionUse Case
defaultRequires approval for everythingMaximum control, manual oversight
acceptEditsAuto-approve file operationsRecommended for most agents
bypassPermissionsAuto-approve everythingTrusted, isolated environments
planPlanning only, no executionResearch agents, dry runs

The most restrictive mode. Every tool use requires explicit approval through herdctl’s permission callback system.

permission_mode: default

When to use:

  • Testing new agents
  • Running untrusted prompts
  • Environments requiring audit trails

Auto-approves file operations (Read, Write, Edit, mkdir, rm, mv, cp) while still requiring approval for other tools like Bash execution. This is the default mode if not specified.

permission_mode: acceptEdits

When to use:

  • Standard development agents
  • Content creation agents
  • Most production use cases

Auto-approves all tool requests without prompting. Use with caution.

permission_mode: bypassPermissions

When to use:

  • Fully trusted agents in isolated environments
  • Docker-isolated agents with resource limits
  • Automated pipelines with pre-validated prompts

Enables planning mode where Claude analyzes and plans but doesn’t execute tools. Useful for understanding what an agent would do.

permission_mode: plan

When to use:

  • Previewing agent behavior before execution
  • Research and analysis agents
  • Generating plans for human review

Control which Claude Code tools an agent can use with allowed_tools and denied_tools arrays. These are top-level configuration fields.

Explicitly list tools the agent can use:

allowed_tools:
- Read
- Write
- Edit
- Glob
- Grep
- Task
- WebFetch
- "Bash(git *)"
- "Bash(npm *)"

Explicitly block specific tools:

denied_tools:
- WebSearch
- WebFetch
- "Bash(sudo *)"
- "Bash(rm -rf /)"
ToolDescriptionRisk Level
ReadRead files from filesystemLow
WriteCreate new filesMedium
EditModify existing filesMedium
GlobFind files by patternLow
GrepSearch file contentsLow
BashExecute shell commands (use patterns)High
TaskLaunch subagentsMedium
WebFetchFetch web contentMedium
WebSearchSearch the webMedium
TodoWriteManage task listsLow
AskUserQuestionRequest user inputLow
NotebookEditEdit Jupyter notebooksMedium

Bash commands are controlled using Bash() patterns in the allowed_tools and denied_tools arrays. The pattern inside the parentheses is matched against the command being executed.

Allow specific commands:

allowed_tools:
- "Bash(git *)" # All git commands
- "Bash(npm run *)" # npm run scripts
- "Bash(pnpm *)" # All pnpm commands
- "Bash(node scripts/*)" # Node scripts in scripts/
- "Bash(make build)" # Specific make target

Deny dangerous patterns:

denied_tools:
- "Bash(rm -rf /)"
- "Bash(rm -rf /*)"
- "Bash(sudo *)"
- "Bash(chmod 777 *)"
- "Bash(curl * | bash)"
- "Bash(curl * | sh)"
- "Bash(wget * | bash)"
- "Bash(wget * | sh)"
- "Bash(dd if=*)"
- "Bash(mkfs *)"
- "Bash(> /dev/*)"

MCP (Model Context Protocol) server tools use the mcp__<server>__<tool> naming convention:

allowed_tools:
- Read
- Edit
- mcp__github__* # All GitHub MCP tools
- mcp__posthog__* # All PostHog MCP tools
- mcp__filesystem__read_file # Specific tool only

Wildcard support:

  • mcp__github__* — Allow all tools from the GitHub MCP server
  • mcp__* — Allow all MCP tools (not recommended)

Full development capabilities with sensible restrictions:

permission_mode: acceptEdits
allowed_tools:
- Read
- Write
- Edit
- Glob
- Grep
- Task
- TodoWrite
- "Bash(git *)"
- "Bash(npm *)"
- "Bash(pnpm *)"
- "Bash(node *)"
- "Bash(npx *)"
- "Bash(tsc *)"
- "Bash(eslint *)"
- "Bash(prettier *)"
- "Bash(vitest *)"
- "Bash(jest *)"
denied_tools:
- "Bash(rm -rf /)"
- "Bash(rm -rf /*)"
- "Bash(sudo *)"
- "Bash(chmod 777 *)"

Can read and search but cannot modify:

permission_mode: default
allowed_tools:
- Read
- Glob
- Grep
- WebFetch
denied_tools:
- Write
- Edit
- Bash

Can read/write files, no shell access:

permission_mode: acceptEdits
allowed_tools:
- Read
- Write
- Edit
- Glob
- Grep
- WebFetch
- WebSearch
denied_tools:
- Bash
- Task

Maximum permissions in a Docker container:

permission_mode: bypassPermissions
allowed_tools: [] # Empty = all tools allowed
docker:
enabled: true
base_image: node:20-slim

Plan and research without execution:

permission_mode: plan
allowed_tools:
- Read
- Glob
- Grep
- WebFetch
- WebSearch

Can only perform git operations:

permission_mode: acceptEdits
allowed_tools:
- Read
- Glob
- Grep
- "Bash(git status)"
- "Bash(git diff *)"
- "Bash(git log *)"
- "Bash(git add *)"
- "Bash(git commit *)"
- "Bash(git push *)"
- "Bash(git pull *)"
- "Bash(git checkout *)"
- "Bash(git branch *)"
- "Bash(git merge *)"
- "Bash(gh pr *)"
- "Bash(gh issue *)"
denied_tools:
- "Bash(git push --force *)"
- "Bash(git push -f *)"
- "Bash(git reset --hard *)"

Begin with minimal permissions and expand as needed:

# Start here
permission_mode: default
allowed_tools:
- Read
- Glob
- Grep
# Add more as you verify behavior
EnvironmentRecommended Mode
Development/Testingdefault
Production (standard)acceptEdits
Production (Docker isolated)bypassPermissions
Research/Previewplan

Always deny dangerous patterns in denied_tools:

denied_tools:
# Destructive commands
- "Bash(rm -rf /)"
- "Bash(rm -rf /*)"
- "Bash(rm -rf ~)"
- "Bash(rm -rf ~/*)"
- "Bash(rm -rf .)"
- "Bash(rm -rf ./*)"
# Privilege escalation
- "Bash(sudo *)"
- "Bash(su *)"
- "Bash(doas *)"
# Remote code execution
- "Bash(curl * | bash)"
- "Bash(curl * | sh)"
- "Bash(wget * | bash)"
- "Bash(wget * | sh)"
- "Bash(eval *)"
# System damage
- "Bash(dd if=*)"
- "Bash(mkfs *)"
- "Bash(fdisk *)"
- "Bash(> /dev/*)"
- "Bash(chmod -R 777 *)"
# Fork bomb
- "Bash(:(){ :|:& };:)"

Only allow necessary MCP tools:

allowed_tools:
# Specific MCP tools, not wildcards
- mcp__github__create_issue
- mcp__github__list_issues
- mcp__github__create_pull_request
# NOT: mcp__github__*

Combine Docker isolation with permissions:

permission_mode: bypassPermissions
docker:
enabled: true
base_image: node:20-slim

Restrict workspace access when possible:

workspace:
root: ~/herdctl-workspace/project-a
# Agent can only access this directory

Review agent permissions periodically:

Terminal window
# Show effective permissions for an agent
herdctl config show --agent my-agent --section permissions

Agent permissions inherit from fleet defaults and can be overridden:

# herdctl.yaml (fleet defaults)
defaults:
permission_mode: acceptEdits
denied_tools:
- WebSearch
- "Bash(sudo *)"
agents/trusted-agent.yaml
# Override mode
permission_mode: bypassPermissions
# Add to allowed tools
allowed_tools:
- WebSearch # Override fleet denial
# Inherits denied_tools from fleet

Inheritance rules:

  1. Agent settings override fleet defaults
  2. denied_tools takes precedence over allowed_tools
  3. Denied bash patterns always apply (never removed by inheritance)

Validate your permission configuration:

Terminal window
# Validate specific agent
herdctl validate agents/my-agent.yaml
# Validate entire fleet
herdctl validate
# Show merged permissions
herdctl config show --agent my-agent --section permissions

// Top-level permission fields (not nested)
permission_mode?: "default" | "acceptEdits" | "bypassPermissions" | "plan"
allowed_tools?: string[]
denied_tools?: string[]
FieldTypeDefaultDescription
permission_modestring"acceptEdits"Permission approval mode
allowed_toolsstring[]Tools the agent can use (including Bash() patterns)
denied_toolsstring[]Tools explicitly blocked (including Bash() patterns)

Bash commands are specified using Bash(<pattern>) syntax:

PatternDescription
Bash(git *)Allow any git command
Bash(npm run build)Allow specific npm script
Bash(node scripts/*)Allow node scripts in specific directory
Bash(sudo *)(Deny) Block all sudo commands
Bash(rm -rf /)(Deny) Block dangerous rm command