cd /news/ai-agents/two-agents-two-worktrees-codex-write… · home › topics › ai-agents › article
[ARTICLE · art-156651] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

Two Agents, Two Worktrees: Codex Writes the Tests Blind, Claude Code Makes Them Pass

A developer demonstrated a two-agent TDD workflow that enforces test/implementation separation through git rather than prompts: Codex writes tests from a spec alone in its own worktree and branch, the tests are committed and cherry-picked into a second worktree where Claude Code implements against them under a deny rule on the test paths, and a single `git diff` verifies the tests were not altered. The setup uses Node's built-in test runner with native TypeScript type stripping and a `.claude/settings.json` deny list covering `/test/**`, `package.json` and `.claude/**`.

by read10 min views1 publishedOct 24, 2026

Ask one agent for parseDuration and its tests in a single prompt, and you get a function and a test file that agree with each other. Whether either of them agrees with your spec is a different question, and the tests can't answer it. They were written by something that already knew what the code would do.

People building TDD workflows for agents keep running into this. A Claude Code TDD write-up on alexop.dev describes how, in one context window, "the test writer's detailed analysis bleeds into the implementer's thinking," and splits the work into separate test-writer and implementer subagents. The tdd-context-split skill, under active development in 2026, is built on "2 agents with minimal sharing": the test author and the implementer "do not share a context, only the spec." Anthropic's own Claude Code best practices put it in one line: "have one Claude write tests, then another write code to pass them."

Those barriers live in prompts and subagent setups. A prompt asks the implementer not to touch the tests. This tutorial moves the wall into git. Codex writes the tests from the spec alone, on its own branch in its own worktree, before any implementation exists. You commit them and cherry-pick that commit into a second worktree. Claude Code works there under a deny rule on the test paths. Then a single git diff tells you whether the tests came back the way Codex left them.

Start from a small TypeScript repo in ~/code/duration. Four files go on main before either agent starts. First the spec, which is all Codex gets:


`src/duration.ts` exports
`parseDuration(input: string): number`.

- Returns whole seconds.
- Units are h, m and s. At least one part is required.
- Parts appear in the order h, m, s, each at most once:
  "1h30m" -> 5400, "45s" -> 45, "2h" -> 7200.
- Leading and trailing whitespace is ignored:
  " 10m " -> 600.
- Anything else throws a RangeError:
  "", "90", "1x", "30m1h", "1h1h", "-5m".

Then a stub, so the import resolves and every test starts red:

// src/duration.ts
export function parseDuration(input: string): number {
  throw new Error(`not implemented: ${input}`);
}

The tests run on Node's built-in runner, so there is nothing to install. node:test has been stable since Node 20, and Node runs .ts files directly since type stripping became the default in Node 22.18.0 and 23.6.0. Type stripping only erases types, so imports need the .ts extension, and enums are out.

{
  "name": "duration",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --test \"test/**/*.test.ts\""
  }
}

The last file is the deny rule, in .claude/settings.json:

{
  "permissions": {
    "deny": [
      "Edit(/test/**)",
      "Edit(/package.json)",
      "Edit(/.claude/**)"
    ]
  }
}

According to the permissions docs, Edit rules "apply to all built-in tools that edit files," and a leading / in project settings anchors the pattern at the session's primary working directory. In a worktree session, that is the worktree's own folder. The package.json line stops a quiet rewrite of the test script, and the .claude/** line keeps the rule file itself out of reach. "Deny rules block in every mode," says the permission modes page, including bypassPermissions. Deny rules in project settings also take effect without the workspace trust step, because they only restrict.

Commit all four files. The rule is there for this experiment. Take it out later if you want Claude writing tests in this repo again.

Give each agent its own worktree, both cut from main:

cd ~/code/duration
git worktree add -b tests/duration ../duration-tests main
git worktree add -b impl/duration ../duration-impl main
cd ../duration-tests
codex --sandbox workspace-write --ask-for-approval on-request

Both flags are from the Codex CLI reference. Then give Codex the task:

Read SPEC.md. Write tests for parseDuration in
test/duration.test.ts using node:test and
node:assert/strict. Import from "../src/duration.ts".
Every expected value must come from SPEC.md. Cover
each example and each RangeError case. Do not create
or change anything outside test/. Run npm test: it
should fail, because src/duration.ts is a stub.

Nothing to peek at exists yet. The only code in the repo is a stub that throws. The /tdd skill on AI Hero has the rule worth holding Codex to: "Expected values have to come from somewhere else: a known-good literal, a worked example, the spec." The kind of test you want back looks like this:

import { test } from "node:test";
import assert from "node:assert/strict";
import { parseDuration } from "../src/duration.ts";

test("combines hours and minutes", () => {
  assert.equal(parseDuration("1h30m"), 5400);
});

test("rejects parts out of order", () => {
  assert.throws(() => parseDuration("30m1h"), RangeError);
});

This tutorial doesn't rely on a path rule for Codex. You check its work with git before anything is committed:

git status --short
npm test
git add test
git commit -m "Tests for parseDuration, from SPEC.md"

git status --short should list paths under test/ and nothing else. npm test should fail. If a test passes against a stub that throws, it isn't testing anything, so send it back. Read the tests here, too. This is the one point in the run where a human checks that they match the spec.

Move to the implementation worktree and bring the test commit in:

cd ../duration-impl
git cherry-pick tests/duration
npm test
claude --permission-mode acceptEdits

git cherry-pick applies the change a commit introduces and records a new commit for it. It needs a clean working tree, which a fresh worktree has. npm test is red again, for the same reason. Now the task for Claude Code:

npm test fails. Make it pass by changing src/ only.
The tests in test/ come from SPEC.md and are fixed:
you can't edit them. If a test looks wrong to you,
stop and tell me which one and why.

An agent that can't edit a test it disagrees with needs a way out other than working around it. The last line of the prompt is that way out.

In acceptEdits mode, Claude Code edits files without asking and also auto-approves mkdir, touch, rm, rmdir, mv, cp and sed inside the working directory (permission modes). Other Bash commands still prompt, apart from a built-in read-only set. The deny rule sits above all of that.

When the suite is green, compare the guarded paths with the commit Codex's tests came from:

git diff --exit-code tests/duration -- \
  test/ package.json .claude/
git status --short -- test/
git diff --stat tests/duration

The first command compares your working tree with the tip of tests/duration on just those paths, and exits non-zero if anything differs. The second catches new, untracked files under test/, which git diff doesn't show. The third shows what Claude changed overall, which should be src/ alone.

You need this check because the deny rule has an edge, and the docs say exactly where it is. Read and Edit deny rules cover Claude's built-in file tools, file commands Claude Code recognizes in Bash "such as cat, head, tail, sed, and tee," and redirect targets like > file. They don't cover "arbitrary subprocesses that read or write files indirectly, like a Python or Node script that opens files itself." For a block at the OS level, the docs point to sandboxing. The rule makes it hard for Claude to edit a test. The diff tells you whether it did anyway.

If all three commands come back clean, commit and merge:

git add src
git commit -m "Implement parseDuration"
cd ~/code/duration
git merge impl/duration
git worktree remove ../duration-tests
git worktree remove ../duration-impl
git branch -d impl/duration
git branch -D tests/duration

git branch -d refuses tests/duration, and it's right to. main holds a cherry-picked copy of the test commit, not the commit itself, so git can't see that branch as merged. Use -D once the diff check has passed.

I build Hermes IDE, so weigh this section with that in mind. Everything above works in plain terminals. Hermes saves you the bookkeeping, and it doesn't replace the git diff check.

Press ⌘N (Ctrl+Shift+N on Windows and Linux), pick Codex in the agent picker, and paste the Step 2 prompt as the task. Hermes starts Codex in a real terminal on its own hermes/ branch in its own worktree, with the task as its first prompt. As in the screenshot below, the sidebar lists each session's branch, so you can read the tests branch name off it. Commit Codex's test files from the Review Desk, which handles stage, unstage and commit for each project.

Codex and Claude Code side by side in Hermes 2.3.4, each in its own hermes/ worktree, with status, context use, cost and branch in the sidebar.

Press ⌘N again and pick Claude Code. Make the first line of the task git cherry-pick <tests branch>, followed by the Step 3 prompt. Once it starts, press Shift+Tab in its terminal to switch to accept-edits mode, the same as in any terminal (permission modes). Claude Code stops to ask before running the git command. The attention inbox counts the agents blocked on you, and ⌘I jumps to the one that has waited longest. You answer in the agent's own terminal, and you see the exact command before it runs. Hermes never answers a permission prompt for you.

When the suite is green, open the Review Desk (⌘G) on Claude's task. It shows changes by turn or by file. The cherry-pick itself shows up in the first turn. Anything under test/ in a later turn means Claude found a way around the rule, and you can leave a line comment that goes straight back to Claude. Run the Step 4 commands in that task's worktree folder too, with Codex's hermes/ branch name in place of tests/duration.

The Review Desk (⌘G) in By turn view: each turn with its files and line counts, the selected turn's diff, and a commit message drafted from the turns.

Then Land. It commits, opens a pull request or merges locally in one step, into the branch the task started from, and it can be undone. Landing Claude's task brings the cherry-picked tests and the implementation into main together. Hermes is free, source-available under BSL 1.1, and runs on macOS, Windows and Linux. It isn't affiliated with Anthropic, OpenAI, Nous Research or any agent vendor.

Git keeps the two agents' contexts apart and keeps the tests from drifting. It can't make the tests good. If Codex misreads the spec, Claude Code will implement the misreading. That's why Step 2 ends with you reading the tests.

It also doesn't stop Claude from overfitting. A function that special-cases "1h30m" passes a test that only checks "1h30m". More spec examples help, and so does reading git diff --stat for anything that looks like a lookup table. I don't have evidence that a split workflow ships fewer bugs. What it does give you is narrower: the tests you grade against were written before the code and stayed put while the code was written.

If you try it, I'd like to know what broke first: Codex's tests, Claude's patience with them, or the deny rule. Tell me in the comments.

A test suite the implementing agent can't edit is a guardrail, and guardrails are half of what AI That Ships is about. The book covers evals, guardrails, cost control and deploying on Node.js, in TypeScript, for LLM features you have to keep running after the demo. If the git diff --exit-code check above felt like the right instinct, the book applies that instinct to everything around the model.

It is book 5 of AI in TypeScript, a five-book series that runs from your first LLM call to agents in production. If you want to run this two-agent setup without juggling terminals, download Hermes IDE or star it on GitHub.

── more in #ai-agents 4 stories · sorted by recency
── more on @codex 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/two-agents-two-workt…] indexed:0 read:10min 2026-10-24 · —