# How Worktree Hooks Enable Isolated Branch-Based Development in Claude-Code-Harness

> Discover how Claude-Code-Harness uses Worktree hooks for isolated branch-based development. Ensure complete filesystem isolation for each worker with WorktreeCreate and WorktreeRemove agents.

- Repository: [Chachamaru/claude-code-harness](https://github.com/Chachamaru127/claude-code-harness)
- Tags: how-to-guide
- Published: 2026-05-28

---

**Claude-Code-Harness implements two agent hooks—`WorktreeCreate` and `WorktreeRemove`—to spin up independent Git worktrees for each parallel worker, ensuring complete filesystem and branch isolation.**

The `claude-code-harness` repository solves the problem of parallel agent collisions by using Git worktrees as sandboxed environments. When agents run with branch-based isolation enabled, the harness intercepts lifecycle events through registered hooks, creating dedicated workspaces that prevent file conflicts and keep unmerged changes confined to individual branches.

## Hook Registration and Event Flow

The worktree system begins with declarative registration in [`.claude-plugin/hooks.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/.claude-plugin/hooks.json). This configuration binds the `WorktreeCreate` and `WorktreeRemove` events to the `bin/harness` binary, which delegates to specific subcommands.

When Claude emits a `WorktreeCreate` event during agent initialization, the runtime invokes `_ hook worktree-create`. The corresponding JSON payload contains three critical fields:

```json
{
  "session_id": "session-abcde-123",
  "cwd": "/tmp/harness-worktrees/12345",
  "hook_event_name": "WorktreeCreate"
}

```

The hook handler validates this input and returns a decision JSON: `{"decision":"approve","reason":"WorktreeCreate: initialized worktree state"}`.

## Creating Isolated Worktrees with WorktreeCreate

The `WorktreeCreate` hook initializes the sandbox environment through two parallel implementations: a Go handler for production use and a shell script for lightweight environments.

### Validating the Working Directory

In [`go/internal/hookhandler/worktree_create.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/internal/hookhandler/worktree_create.go), the `hookhandler.HandleWorktreeCreate` function parses the incoming payload and immediately validates the `cwd` parameter. The helper function `looksLikeHookDecisionJSON` prevents a critical bug (fixed in issue #128) where malformed JSON could be interpreted as a directory path.

```go
// normalizeWorktreeCreateCWD guarantees that decision JSON 
// cannot be treated as a directory path
func normalizeWorktreeCreateCWD(cwd string) (string, error) {
    if looksLikeHookDecisionJSON(cwd) {
        return "", fmt.Errorf("cwd appears to be JSON, not a path")
    }
    return cwd, nil
}

```

### Persisting State Metadata

After validation, the handler creates a hidden state directory at `$CWD/.claude/state` and writes [`worktree-info.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/worktree-info.json). This file tracks the worker ID, timestamp, and working directory, ensuring the harness can correlate filesystem paths with specific agent sessions.

The shell implementation in [`scripts/hook-handlers/worktree-create.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/scripts/hook-handlers/worktree-create.sh) mirrors this logic for environments without the Go binary:

```bash
#!/bin/bash

# scripts/hook-handlers/worktree-create.sh

if [ -z "$INPUT" ]; then
  echo '{"decision":"approve","reason":"WorktreeCreate: no payload"}'
  exit 0
fi

SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""')
CWD=$(echo "$INPUT" | jq -r '.cwd // ""')

# Reject a cwd that looks like a decision JSON

if [[ "$CWD" =~ ^\{.*\"decision\".*\"reason\".*\}$ ]]; then
  echo '{"decision":"approve","reason":"WorktreeCreate: invalid cwd"}'
  exit 0
fi

# Initialise state dir and info file

WORKTREE_STATE_DIR="${CWD}/.claude/state"
mkdir -p "$WORKTREE_STATE_DIR"
cat > "${WORKTREE_STATE_DIR}/worktree-info.json" <<EOF
{"worker_id":"$SESSION_ID","created_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","cwd":"$CWD"}
EOF

echo '{"decision":"approve","reason":"WorktreeCreate: initialized worktree state"}'

```

## Managing Worktree Lifecycles with WorktreeManager

Behind the hooks, the `breezing.WorktreeManager` in [`go/internal/breezing/worktree.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/internal/breezing/worktree.go) handles the actual Git operations. When a worker requests isolation, the manager executes:

```bash
git worktree add -b <branch> <path> HEAD

```

This creates a linked working tree with its own branch, allowing the agent to commit changes without affecting the main checkout. The manager records the path, branch, task ID, and owning agent ID in an internal registry.

Programmatic usage follows this pattern:

```go
wm := breezing.NewWorktreeManager("/path/to/repo")
path, err := wm.Create("task-123", "")          // creates .harness-worktrees/task-123
if err != nil { log.Fatal(err) }

wm.AssignAgent(path, "worker-xyz")              // associate the agent
// ... run the agent inside `path` ...

wm.MarkInactive(path)                           // when the agent stops
wm.CleanupStale()                               // removes worktrees older than 24h

```

## Cleaning Up Resources with WorktreeRemove

When a worker terminates, the `WorktreeRemove` hook fires. The Go handler in [`go/internal/hookhandler/worktree_remove.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/internal/hookhandler/worktree_remove.go) removes temporary Codex prompt files from `/tmp`, cleans harness logs, and deletes the [`worktree-info.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/worktree-info.json) marker file.

The shell counterpart in [`scripts/hook-handlers/worktree-remove.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/scripts/hook-handlers/worktree-remove.sh) performs identical cleanup:

```bash
#!/bin/bash

# scripts/hook-handlers/worktree-remove.sh

# ... read INPUT, extract SESSION_ID and CWD ...

# Remove temporary Codex prompt files and harness logs

rm -f /tmp/codex-prompt-*.md /tmp/harness-codex-*.log

# Delete the worktree-specific info file

if [ -n "$CWD" ] && [ -f "$CWD/.claude/state/worktree-info.json" ]; then
  rm -f "$CWD/.claude/state/worktree-info.json"
fi

echo '{"decision":"approve","reason":"WorktreeRemove: cleaned up worktree resources"}'

```

## Enabling Isolation Mode in Agent Configuration

Agents opt into branch-based isolation by declaring `isolation: "worktree"` in their skill definitions. When this flag is set, the harness supplies each agent with a unique worktree path from `WorktreeManager` rather than the main repository checkout.

This configuration ensures:
- **Filesystem isolation**: Edits in `.claude/state` and working files remain confined to the worker's directory
- **Branch isolation**: Commits occur on the worktree's branch, leaving `main` untouched
- **Parallel safety**: Multiple workers can modify the same filenames without conflict

Trigger isolation manually via CLI:

```bash
claude agents --isolation=worktree --task-id=12345

```

## Safety Guarantees and Error Handling

The worktree hook system includes several defensive mechanisms to prevent resource leaks and security issues:

- **Input sanitization**: The `normalizeWorktreeCreateCWD` function rejects payloads where the cwd parameter resembles JSON, eliminating the bug described in issue #128
- **Automatic pruning**: `WorktreeManager.CleanupStale` removes abandoned worktrees older than 24 hours, preventing repository bloat
- **Idempotent cleanup**: `WorktreeRemove` handlers safely ignore missing files, ensuring repeated cleanup calls do not error
- **Resource containment**: All temporary files are scoped to `/tmp` and the specific worktree path, preventing cross-contamination between agents

## Summary

- **Worktree hooks** (`WorktreeCreate` and `WorktreeRemove`) in `claude-code-harness` automate the lifecycle of isolated Git worktrees for parallel agents
- **Hook registration** occurs through [`.claude-plugin/hooks.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/.claude-plugin/hooks.json), delegating to `bin/harness` and specialized handlers in `go/internal/hookhandler/`
- **State management** happens via hidden `.claude/state` directories containing [`worktree-info.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/worktree-info.json) metadata files
- **Git isolation** is provided by `breezing.WorktreeManager`, which executes `git worktree add -b` to create branch-specific workspaces
- **Cleanup automation** removes temporary Codex files and state markers when workers terminate, with stale worktree pruning handled by `CleanupStale`

## Frequently Asked Questions

### How do I enable worktree isolation for a specific Claude agent?

Set the `isolation` field to `"worktree"` in the agent's skill definition or launch the agent with the `--isolation=worktree` flag. According to the `claude-code-harness` source code, this triggers the `WorktreeCreate` hook during agent initialization, which allocates a separate Git worktree via the `breezing.WorktreeManager`.

### What happens if the WorktreeCreate hook receives malformed JSON?

The Go implementation in [`go/internal/hookhandler/worktree_create.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/internal/hookhandler/worktree_create.go) includes the `looksLikeHookDecisionJSON` validation function. If the `cwd` parameter appears to be JSON rather than a filesystem path (a bug fixed in issue #128), the handler rejects the input and returns an approval response with reason `"WorktreeCreate: invalid cwd"`, preventing the creation of directories with malformed names.

### Where does the harness store metadata about active worktrees?

Each worktree contains a hidden state directory at [`.claude/state/worktree-info.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/.claude/state/worktree-info.json) created by both the Go handler and the shell script in [`scripts/hook-handlers/worktree-create.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/scripts/hook-handlers/worktree-create.sh). This JSON file stores the worker ID, creation timestamp, and absolute path, allowing the `WorktreeManager` to track which agents own specific branches.

### Can I use the worktree hooks without the Go binary installed?

Yes. The repository provides POSIX-compliant shell implementations in [`scripts/hook-handlers/worktree-create.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/scripts/hook-handlers/worktree-create.sh) and [`scripts/hook-handlers/worktree-remove.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/scripts/hook-handlers/worktree-remove.sh). These scripts handle the same JSON parsing, validation, and state directory creation as the Go handlers, making the isolation system available in environments where the compiled `bin/harness` binary is not present.