Understanding the Coding Harness

How Claude Code runs tools safely, isolates execution, and manages permissions

Background: Claude Code Architecture

Claude Code is an environment where Claude can interact with your system—reading files, running commands, calling APIs, and more. But Claude runs in the cloud. Your system is local. How does Claude actually execute code on your machine safely?

The answer is the Claude Code Harness: a runtime manager that sits between Claude's decisions and your system's resources. Think of it as a careful gatekeeper.

The Harness is not new infrastructure you build. It's already part of Claude Code. Your job is to understand its boundaries and configure its permissions.

Before the harness existed, there would be three options: either Claude could do anything (dangerous), Claude could do nothing (useless), or someone would manually approve each command (tedious). The harness automates the "who decides what's allowed" question using a configuration layer.

Intuition: What Is a Harness?

Imagine you're hiring a contractor to fix your house. You wouldn't give them unlimited access—you'd say "you can use the kitchen, the hallway, and the bathroom; you cannot touch the bedroom." That set of rules is a harness.

The contractor (Claude) decides what work to do (fix the sink). The harness (your rules) decides whether that work is allowed (yes, if the sink is in the kitchen). If the contractor tries to paint the bedroom (not allowed), the harness stops it.

Here's a concrete example:

// Claude decides: "I'll read the config file"
const content = await bash('cat /src/config.json');

// Harness checks: "Is /src readable?"
// Harness config says: "Yes, /src is readable"
// Result: Harness allows it, returns file content

// Claude decides: "I'll delete the system folder"
const result = await bash('rm -rf /etc');

// Harness checks: "Is /etc writable?"
// Harness config says: "No, /etc is not writable"
// Result: Harness blocks it, returns Permission Denied

This is the core insight: Claude proposes actions, the harness enforces policy.

The Three Kinds of Tools

The harness manages three kinds of operations:

Tool Type Example What Harness Controls
Bash bash('ls -la /home') Which directories are readable/writable, which commands are allowed
MCP Servers invokeToolViaAgent('github') Which MCP servers are reachable, which tool parameters are allowed
File I/O read('/src/file.txt') Which paths can be read, which can be written, file size limits

All three flow through the same harness enforcement: request → permission check → execute → collect output → filter → return result.

Responsibility: Harness vs. Claude

This is the most important concept. The harness and Claude have different jobs.

Claude decides WHAT. Which tool to call. What parameters to pass. Whether the goal makes sense.
Harness decides IF and HOW. Whether that action is allowed. Which sandbox to run it in. How to isolate its effects.

Example scenario: Claude wants to install a package.

Claude's decision: "This project needs the 'express' library. I'll run npm install express"
Harness's decision: "Can npm run? Can it write to node_modules? Is the internet available? Timeout is 60s."

Two possibilities:

1. Config allows npm, node_modules is writable:
   → Harness: "Allowed. Here's the result."
   → Claude: "Great, now I can use express"

2. Config disallows npm:
   → Harness: "Permission denied: npm is not in allowlist"
   → Claude: "Oh, I can't run npm here. Let me try a different approach"
Common misconception: "If the harness blocks something, Claude doesn't know why." Wrong. Claude gets a structured error: {success: false, error: "PermissionError", message: "npm not in allowlist"}. Claude can then make a different decision.

This division of responsibility is why Claude Code can be both powerful and safe. Claude has the intelligence to solve problems. The harness has the policy to prevent harm.

The Permission Model

The harness doesn't reinvent authorization. It uses a simple allowlist + blocklist model for each kind of resource.

Harness Permission Layers
File I/O

/src ✓ readable
/etc ✗ writable
/tmp ✓ read/write

Tool Execution

bash ✓ allowed
docker ✗ blocked
npm ✓ allowed

Network

localhost ✓ allowed
api.example.com ✓ allowed
* ✗ blocked by default

How Permission Checking Works

When Claude makes a request, the harness runs a series of checks in order. First match wins.

REQUEST: bash('cat /src/readme.md')

CHECKS:
1. Is 'bash' in the tool allowlist? → YES
2. Is '/src' readable? → YES
3. Any environment restrictions? → NO
4. Timeout OK (120s)? → YES

RESULT: Permission granted. Execute command.

If any check fails, the request is denied:

REQUEST: bash('docker run ubuntu')

CHECKS:
1. Is 'bash' in the tool allowlist? → YES
2. Is '/src' readable? → N/A (not accessing files)
3. Any environment restrictions? → NO
4. Timeout OK (120s)? → YES

RESULT: Permission granted. Execute bash.

BUT THEN:

SUBPROCESS CHECK: bash tries to run 'docker'
1. Is 'docker' in the tool allowlist? → NO

RESULT: Permission denied. Bash returns error.
Important: The harness checks tool invocation, but it also checks what happens inside that invocation. If you run bash('rm -rf /etc') and the harness has blocked /etc writes, the filesystem itself will deny it—double protection.

Error Isolation & Resilience

Here's where the harness earns its keep: when something fails, only that operation fails. The harness keeps running.

Without a harness, if Claude ran a bad command, the whole session could crash. With the harness, Claude gets a structured error and can decide what to do next.

Error Flow Through the Harness
SCENARIO 1: Permission Denied
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Claude: "Read /root/secret.txt"
Harness: "Permission denied: /root not readable"
Claude: "OK, I'll read /home instead"
Harness: "OK, here's /home/file.txt"
→ SESSION CONTINUES

SCENARIO 2: Tool Timeout
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Claude: "Run npm install (large project)"
Harness: [executing...]
After 120s: "Timeout: npm did not complete in time"
Claude: "I'll try with --legacy-peer-deps"
Harness: "OK, retrying"
→ SESSION CONTINUES

SCENARIO 3: Command Fails
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Claude: "Build the project"
Bash: "Build failed: syntax error in src/main.js"
Harness: "Tool failed with exit code 1. Here's stderr."
Claude: "Found the error. Let me fix it."
→ SESSION CONTINUES

The key is that errors are data, not crashes. The harness returns structured errors:

{
  success: false,
  error: "PermissionError",
  message: "File not readable: /etc/passwd",
  toolName: "bash",
  exitCode: 1,
  stderr: "bash: /etc/passwd: Permission denied",
  timestamp: "2026-08-17T14:30:00Z"
}

Claude sees this structured response and adapts. This is resilience by design.

Why this matters: Without error isolation, a single bad command could wedge your entire session. With it, Claude can try, fail, learn, and retry. That's the difference between a tool and a paper weight.

Configuration & Best Practices

The harness is configured via your project's .claude/settings.json or .claude/settings.local.json. You control what's allowed.

Principle of Least Privilege

The golden rule: grant the minimum permissions Claude needs to do the job. Not more, not less.

Common mistake: "Let me just allow everything." Don't. Narrow permissions force you to think about what Claude actually needs. When Claude can't do something, that's information—maybe it's not supposed to do that.

Configuration Example

// .claude/settings.json
{
  "permissions": {
    "bash": ["enabled"],
    "allowedTools": ["bash", "npm"],
    "fileAccess": {
      "read": ["/src", "/docs"],
      "write": ["/tmp", "/src/dist"],
      "deny": ["/etc", "/root", "/home"]
    },
    "network": {
      "allow": ["api.github.com", "registry.npmjs.org"],
      "deny": ["*"]
    },
    "timeouts": {
      "bash": 120,
      "mcp": 30,
      "fileIo": 10
    }
  }
}

What this does:

When Claude Hits a Permission Boundary

This is normal and useful. Claude will see an error like:

Error: Permission denied: docker not in allowlist

Claude's next thought: "I can't run docker. Let me try building
with npm instead, or ask the user to enable docker if they want
container builds."

This is the harness working as intended. Constraints are features, not bugs.

Quiz: Test Your Understanding

These questions check whether you understand the core concepts. Read carefully—each question has a correct answer and a reason why.

1. Claude wants to delete a file. The harness allows bash but blocks writes to /etc. What happens?
2. What is the primary responsibility of the harness?
3. Claude runs a bash command that fails. What does the harness do?
4. Your harness config disallows docker but allows bash. Claude tries to run 'docker build'. What happens?
5. Why is "principle of least privilege" important for harness configuration?