Skip to content
CP
Writing
9 min read#code

Building Your Own Claude Code Harness — Don't Tame It Every Session, Carve It Into the Repo

Professionals don't re-tame Claude from scratch each session. They carve their way of working into the .claude/ directory. How to weave memory, commands, agents, and hooks into a harness that automates your recurring work end to end.

The last post (Using Claude Code Like a Pro) covered three knobs — context, permissions, feedback loops — and ten techniques for working them.

But if you're setting up those ten by hand every session, something's wrong. Explaining the same conventions again, typing the same commands again, asking for "review it" again. That's like rewriting your .zshrc from scratch every morning.

Professionals don't. They carve their way of working in once and reuse it. That carved-in scaffolding around the model — the whole rig — is called a harness.

This post is about freezing those ten scattered techniques into one system. The result isn't abstract — it's a .claude/ directory inside your repo, that is, version-controlled code.

The mental model — a harness answers four questions

A good harness answers four questions for Claude, automatically, on every task. And each answer lives in a specific file or folder in the repo.

What do I always know?    →  CLAUDE.md                  (memory)
What procedures can I run? →  .claude/commands/          (commands)
Who do I delegate to?      →  .claude/agents/            (specialists)
What can't I break?        →  .claude/settings.json + hooks  (laws)

The whole shape:

project/
├── CLAUDE.md                 # memory, loaded automatically every session
└── .claude/
    ├── settings.json         # permissions, hooks, env — the control plane
    ├── commands/             # /commands — reusable procedures
    │   ├── ship.md
    │   └── fix-issue.md
    ├── agents/               # subagents — your roster of specialists
    │   ├── explorer.md
    │   └── code-reviewer.md
    └── hooks/                # hook scripts — the rules that can't be broken
        ├── format.sh
        └── guard.sh

Remember one property above all — a harness is code. Instead of taming it by hand each time, you write it once and commit it — and every future task, every teammate, and you-of-a-year-from-now starts on top of it. It compounds.

1. Start from the pain — don't build the whole thing

The most common failure: trying to design an elaborate harness up front. You build 20 agents and 30 commands — and use half of them.

Go the other way. Grow it from friction, not by planning. The rule is simple:

If you've corrected Claude the same way twice — that's the signal to freeze it into the harness.

  • "Left a console.log again" → one line in CLAUDE.md
  • "Explained 'tests first' again" → a /tdd command
  • "Wrong formatting again" → a PostToolUse hook
  • "Burned context searching the whole codebase again" → an explorer agent

A harness grows from friction you actually hit. Configuration for problems you haven't had is just dead weight.

2. CLAUDE.md — the spine of the harness

The memory layer. Because it loads every session, it must be short and accurate. Bloat eats context and buries what matters.

When it threatens to grow, split it and pull pieces in with @:

# CLAUDE.md
 
## Commands
- Build `npm run build` · Test `npm test` · Lint `npm run lint`
 
## Core rules
- Never mutate objects — always return a new one
- Files 200–400 lines, 800 max
- Lint + test must pass before commit
 
## Deep docs (expanded only when needed)
Architecture: @docs/architecture.md
DB schema: @docs/db-schema.md

Write @docs/architecture.md and that file gets pulled into context when the work needs it. The CLAUDE.md body stays lean while the depth lives elsewhere.

Remember the hierarchy too — global (~/.claude/CLAUDE.md, your taste) and project (CLAUDE.md, team-shared) are merged. Personal taste in global, team rules in the project.

3. Slash commands — freezing your procedures

If you keep explaining a multi-step task, freeze it into a command. .claude/commands/ship.md:

---
description: Verify changes, then commit and push
argument-hint: [commit message]
allowed-tools: Bash(npm run *), Bash(git *)
---
 
Wrap up the current changes:
 
1. Run `npm run lint` and `npm test` — stop and report on failure
2. `git diff` to check for unintended changes
3. Summarize the changes and commit (message: $ARGUMENTS)
4. Push the current branch

Now /ship "feat: add login" runs verify → commit → push as one move. $ARGUMENTS carries the message, and allowed-tools pins down which tools this command may use.

Inside a command, you can weave in other tools too — ! injects shell output, @ pulls in file contents:

---
description: Take a GitHub issue and fix it
---
Issue body: !`gh issue view $1`
Relevant conventions: @CLAUDE.md
 
Handle the issue above — write a failing test first, make it pass,
then wrap up with /ship.

4. Subagents — your roster of specialists

If a command is a "procedure," an agent is a "specialist." Each agent gets isolated context + its own tool permissions + a model of its own. Build a roster of the roles you reach for.

A read-only explorer — for searching the codebase without dirtying your context:

<!-- .claude/agents/explorer.md -->
---
name: explorer
description: Searches the codebase to find "where is what." Reports conclusions only.
tools: Read, Grep, Glob
model: haiku
---
 
You are a code search expert. Find what's requested — but do not edit
files. Report only the locations (file:line) and a one-line summary, concisely.

Two design points here:

  • Keep tools narrow — explorer reads only. With no write permission, it can't cause damage.
  • Match model to the task — simple search is fine on haiku: fast and cheap. Reserve sonnet/opus for reviewers that need real reasoning.

A typical roster: explorer (search), test-writer (write tests), code-reviewer (review), security-reviewer (security). Your main session becomes the orchestrator that hands them work and takes back conclusions.

5. Hooks — the laws that can't be broken

A CLAUDE.md rule is something Claude "tries" to honor. Sometimes it forgets. Hooks are different — the code runs no matter what. Not a request, a law.

Keep hook logic in scripts. Auto-format after file edits, .claude/hooks/format.sh:

#!/usr/bin/env bash
# the edited file path arrives as JSON on stdin
file=$(jq -r '.tool_input.file_path // empty')
[ -z "$file" ] && exit 0
case "$file" in
  *.ts|*.tsx|*.js|*.jsx|*.json|*.md) npx prettier --write "$file" ;;
esac

And the most powerful pattern — a guard hook. It blocks dangerous actions outright. .claude/hooks/guard.sh:

#!/usr/bin/env bash
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
  *.env|*.env.*|*/secrets/*)
    echo "Refusing to edit a protected file: $file" >&2
    exit 2 ;;   # exit 2 in PreToolUse → block the action, pass the reason to Claude
esac
exit 0

Wire them up by moment in settings.json:

// .claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard.sh" }] }
    ],
    "PostToolUse": [
      { "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh" }] }
    ]
  }
}

Hooks are the last piece of the harness because with hooks, you can turn on auto-accept. When a guard hook protects .env, a format hook protects style, and a Stop hook protects tests — Claude can't cross the line no matter what it does. Only then can you safely go fast.

6. settings.json — the control plane

Where permissions, hooks, and env converge. The permissions part is stronger read alongside your commands and agents:

{
  "permissions": {
    "allow": ["Bash(npm run test:*)", "Bash(npm run lint)", "Read", "Edit"],
    "deny": ["Read(./.env)", "Bash(rm -rf *)"]
  },
  "hooks": { /* as above */ }
}

Settings have a hierarchy too — in priority order:

  • .claude/settings.local.json — your personal settings, not committed (add to .gitignore)
  • .claude/settings.json — team-shared settings, committed to git
  • ~/.claude/settings.json — shared across all your projects

Team rules in settings.json, personal taste in settings.local.json. That split is what makes a harness shareable.

7. A harness is a team asset — share it, reuse it

The moment you commit .claude/ to the repo, the harness stops being just yours.

  • Teammates — clone the repo and inherit the same harness as-is. A new hire starts on day one with a Claude that already knows the team's conventions, commands, and guards.
  • CI — run the same harness headless. Non-interactive execution like claude -p "/ship" makes it usable for overnight jobs and PR automation, unchanged.
  • Across projects — reuse that crosses repos goes into plugins / skills. Bundle your go-to commands and agents into a plugin and pull it into any repo. (For what to install, see Claude Code plugins worth installing.)

Weave it all together — one task, end to end

With the harness in place, a teammate's day flows like this. It starts from one command:

/fix-issue 128

Everything after that happens automatically:

1. The command reads the issue via `gh issue view 128`        (commands/fix-issue.md)
2. The explorer agent finds the code, reports conclusions only (agents/explorer.md, haiku)
3. Failing test → implement → loop until npm test is green     (feedback loop)
4. prettier runs on every file save                            (hooks/format.sh)
5. Touching .env gets blocked instantly                        (hooks/guard.sh)
6. The code-reviewer agent reviews the changes                 (agents/code-reviewer.md)
7. /ship runs lint · test · commit · push                      (commands/ship.md)

The teammate never read CLAUDE.md, never memorized a convention, never granted tools one by one. The harness already knows. That's what it means to freeze ten scattered techniques into a system.

Five common traps

1. Building it all up front 20 agents you never use. → Grow it one piece at a time, from friction.

2. Harness rot Rules that point to deleted files, commands that lie. A lying harness is worse than none. → Maintain it like code, on a schedule.

3. CLAUDE.md bloat A novel-length memory that eats context. → Keep it lean, push depth behind @.

4. Auto-accept with no guards Auto-accept without hooks is a tightrope with no net. → Guard hooks first, speed second.

5. Not committing .claude/ (or committing it with secrets) The harness doesn't spread, or your keys leak. → Commit shared settings, separate personal and secret.

Closing — the real secret

Through git and the ten Claude Code techniques, the last sentence of this series is this:

The engineer who's good with AI isn't the one writing better prompts — it's the one building a better harness.

Every session, you don't just produce code — you get to refine the machine that produces code. One rule you fixed today, one guard hook you added, pays back with compound interest on every task to come. So a harness isn't a byproduct, it's a first-class artifact. Review it, refactor it, version it.

When you're stuck, there's now one more question to ask — "Does Claude know enough? Does it have permission? Does it have a way to check?" And — "If I'm doing this a second time, isn't it time to carve it into the harness?"

The next post pushes the harness one step further: agents that run without a human pressing start each time — autonomous loops and overnight agents. Once the harness is solid enough, the agent works while you sleep.

Related writing