Skip to main content

Playbook

Production-ready configurations for every team size, deployment target, and security posture. Copy, adapt, ship.

Permission Architectures by Team Size

Solo Developer — Maximum Speed

Optimize for velocity. Allow common tools, deny sensitive file access.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "defaultMode": "acceptEdits",
    "allow": [
      "Bash(npm *)",
      "Bash(git *)",
      "Bash(node *)",
      "Bash(npx *)",
      "Bash(pnpm *)",
      "Edit",
      "Write"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)"
    ]
  }
}

Why acceptEdits: Auto-accepts file writes in the working directory. You're watching the terminal anyway. The deny rules protect secrets even if you step away.

Small Team (2-10) — Balanced

Committed to .claude/settings.json. Every collaborator gets the same guardrails.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "defaultMode": "default",
    "allow": [
      "Bash(npm run *)",
      "Bash(npm test *)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Bash(git status *)",
      "Bash(* --version)",
      "Bash(* --help *)"
    ],
    "deny": [
      "Bash(git push *)",
      "Bash(git push)",
      "Bash(rm -rf *)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Read(./config/credentials.*)"
    ]
  }
}

Design rationale: Read-only git operations are allowed. Network commands and destructive operations require human approval. Every developer can add personal overrides in .claude/settings.local.json without affecting the team.

Enterprise — Locked Down

Deployed via managed settings (/etc/claude-code/managed-settings.json). Cannot be overridden by anyone.

{
  "permissions": {
    "disableBypassPermissionsMode": "disable",
    "deny": [
      "Bash(curl *)",
      "Bash(wget *)",
      "Bash(ssh *)",
      "Bash(scp *)",
      "Bash(git push --force *)",
      "Bash(git reset --hard *)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Read(~/.ssh/**)",
      "Read(~/.aws/**)"
    ]
  },
  "allowManagedPermissionRulesOnly": true,
  "disableAutoMode": "disable",
  "availableModels": ["sonnet", "haiku"],
  "model": "sonnet",
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "allowUnsandboxedCommands": false,
    "network": {
      "allowManagedDomainsOnly": true,
      "allowedDomains": ["github.com", "*.npmjs.org", "registry.yarnpkg.com"]
    },
    "filesystem": {
      "allowManagedReadPathsOnly": true,
      "denyRead": ["~/.ssh", "~/.aws", "~/.gnupg"]
    }
  }
}

Key decisions: allowManagedPermissionRulesOnly prevents users from adding their own allow rules. disableBypassPermissionsMode blocks the most permissive mode. The sandbox provides OS-level enforcement that tool-level deny rules cannot.

Model Selection Strategies

Cost Optimization

Pin to cheaper models. Restrict the picker so users cannot accidentally switch to Opus.

{
  "model": "sonnet",
  "availableModels": ["sonnet", "haiku"],
  "effortLevel": "low",
  "env": {
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5"
  }
}

Speed Optimization

Sonnet with medium effort and tight output limits for fast iteration cycles.

{
  "model": "sonnet",
  "effortLevel": "medium",
  "env": {
    "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "4096",
    "BASH_DEFAULT_TIMEOUT_MS": "60000"
  }
}

Quality Optimization

Opus with 1M context and high effort for complex architectural work.

{
  "model": "opus[1m]",
  "effortLevel": "high",
  "env": {
    "MAX_THINKING_TOKENS": "32768"
  }
}

Hybrid — Best of Both Worlds

Opus reasons about the approach, Sonnet executes it. Best cost/quality ratio for complex tasks.

{
  "model": "opusplan"
}

Model Decision Matrix

ScenarioModelEffortCost
Bug triage, code reviewsonnetmediumLow
Feature implementationsonnetmediumLow
Architecture designopushighHigh
Multi-file refactoropusplanhighMedium
CI/CD automationsonnetlowMinimal
Large codebase explorationsonnet[1m]mediumLow
Subagent taskshaikulowMinimal

Admin Model Restriction

Lock users to specific models and pin alias targets to prevent version drift:

{
  "availableModels": ["sonnet", "haiku"],
  "model": "sonnet",
  "env": {
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-5"
  }
}

This pins the sonnet alias to a specific version, limits the model picker, and prevents automatic upgrades.

Profile Configurations

Work Profile

~/.claude-work/settings.json — routed through a corporate LLM gateway.

{
  "model": "sonnet",
  "permissions": {
    "defaultMode": "acceptEdits",
    "allow": [
      "Bash(npm *)",
      "Bash(git *)",
      "Bash(docker *)",
      "Bash(kubectl *)"
    ],
    "deny": [
      "Bash(git push --force *)",
      "Read(./.env.production)"
    ]
  },
  "env": {
    "ANTHROPIC_BASE_URL": "https://llm-gateway.company.com/v1"
  },
  "language": "english"
}

Personal Profile

~/.claude-personal/settings.json — maximum capability for side projects.

{
  "model": "opus",
  "permissions": {
    "defaultMode": "bypassPermissions"
  },
  "effortLevel": "high"
}

Switching Profiles

alias claude-work='CLAUDE_CONFIG_DIR=~/.claude-work claude'
alias claude-personal='CLAUDE_CONFIG_DIR=~/.claude-personal claude'

Add the active profile to your shell prompt to avoid confusion:

export PS1="[claude:${CLAUDE_PROFILE:-default}] $PS1"

Security-Focused Configurations

Block Network Operations

{
  "permissions": {
    "deny": [
      "Bash(curl *)",
      "Bash(wget *)",
      "Bash(nc *)",
      "Bash(ncat *)",
      "Bash(ssh *)",
      "Bash(scp *)",
      "Bash(rsync *)",
      "WebFetch"
    ]
  }
}

Protect Sensitive Files

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Read(./config/credentials.*)",
      "Read(~/.ssh/**)",
      "Read(~/.aws/**)",
      "Read(~/.gnupg/**)",
      "Edit(./.github/workflows/**)"
    ]
  }
}

Warning: Read/Edit deny rules only block Claude's built-in tools, NOT Bash commands. Read(./.env) blocks the Read tool but does NOT block cat .env in Bash. For true enforcement, enable the sandbox.

Defense-in-Depth with Sandbox

Layer tool-level deny rules with OS-level sandbox enforcement:

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)"
    ]
  },
  "sandbox": {
    "enabled": true,
    "autoAllowBashIfSandboxed": true,
    "filesystem": {
      "denyRead": ["~/.aws/credentials", "~/.ssh"],
      "allowWrite": ["/tmp/build", "."],
      "denyWrite": ["/etc", "/usr/local/bin"]
    },
    "network": {
      "allowedDomains": ["github.com", "*.npmjs.org"],
      "allowLocalBinding": true
    }
  }
}

The sandbox blocks cat .env at the OS level. The deny rules block the Read tool at the application level. Both layers must be bypassed for a breach.

CI/CD Configuration

Headless Execution

#!/bin/bash
export ANTHROPIC_API_KEY="${{ secrets.ANTHROPIC_API_KEY }}"
export DISABLE_TELEMETRY=1
export DISABLE_AUTOUPDATER=1
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
export CLAUDE_CODE_SKIP_PROMPT_HISTORY=1
 
npx @anthropic-ai/claude-code -p "Review the PR and suggest improvements" \
  --model sonnet \
  --permission-mode bypassPermissions \
  --output-format json \
  --no-session-persistence \
  --bare \
  --allowedTools "Read,Grep,Glob,Bash(git diff *),Bash(npm test *)"

A complete GitHub Actions workflow:

# .github/workflows/claude-review.yml
name: Claude Code PR Review
on:
  pull_request:
    types: [opened, synchronize]
 
jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Claude Code Review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          ANTHROPIC_MODEL: sonnet
          DISABLE_TELEMETRY: "1"
          DISABLE_AUTOUPDATER: "1"
          CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
          CLAUDE_CODE_SKIP_PROMPT_HISTORY: "1"
        run: |
          REVIEW=$(npx @anthropic-ai/claude-code -p \
            "Review the PR diff. Focus on bugs, security, and performance. Output markdown." \
            --model sonnet \
            --permission-mode bypassPermissions \
            --output-format text \
            --no-session-persistence \
            --bare \
            --allowedTools "Read,Grep,Glob,Bash(git diff *),Bash(git log *)")
 
          gh pr comment ${{ github.event.pull_request.number }} --body "$REVIEW"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Key CI Flags

FlagPurpose
-pNon-interactive/headless mode
--bareSkip auto-discovery of hooks, skills, plugins, MCP, CLAUDE.md
--no-session-persistenceDon't write transcripts
--output-format jsonStructured output for pipeline parsing
--allowedToolsExplicit tool allowlist — nothing else runs
--permission-mode bypassPermissionsSkip prompts (safe in ephemeral containers)

Why bypassPermissions is safe in CI: The container is ephemeral. There are no secrets on disk beyond what you inject. The --allowedTools flag restricts the tool set. The container dies after the run.

Auto Mode Configuration

Configure what the background classifier trusts:

{
  "autoMode": {
    "environment": [
      "Organization: Acme Corp. Primary use: software development",
      "Source control: github.com/acme-corp",
      "Trusted cloud buckets: s3://acme-build-artifacts",
      "Trusted internal domains: *.internal.acme.com"
    ]
  }
}

The classifier reads autoMode from user settings, .claude/settings.local.json, and managed settings. It does NOT read from shared project settings (.claude/settings.json) — this prevents repo injection of allow rules.

Critical: Setting autoMode.soft_deny or autoMode.allow replaces the ENTIRE default list. Always run claude auto-mode defaults first, copy the full lists, then edit.

# Step 1: Export current defaults
claude auto-mode defaults > auto-mode-defaults.json
 
# Step 2: Edit the file — ADD your entries to existing lists
# Step 3: Apply in ~/.claude/settings.json or .claude/settings.local.json
{
  "autoMode": {
    "environment": [
      "Organization: Acme Corp. Primary use: software development",
      "Source control: github.com/acme-corp",
      "Trusted cloud buckets: s3://acme-build-artifacts"
    ],
    "allow": [
      "Bash(npm run *)",
      "Bash(git diff *)",
      "Bash(git status *)",
      "Read"
    ],
    "soft_deny": [
      "Bash(git push --force *)",
      "Bash(curl * | bash)",
      "Bash(rm -rf /)",
      "Bash(*production*)"
    ]
  }
}