One Repo, Two Agents, No ~/.claude: 5 Rules for Making Claude Code and Codex Behave the Same

What I found when I tried to make a repository carry its own agent rules for both runtimes

My agents are well trained, as long as they run on my Mac. On 2026-10-01 my ~/.claude held a 22,449-byte CLAUDE.md, 12 rule files, 7 agents, hooks on five events and 24 enabled plugins. My ~/.codex held a 31,128-byte AGENTS.md, 11 hook files, 20 agents and its own skills.

A cloud session doesn't have either directory, and neither does a CI job, a second machine or someone else's clone. An agent there loads none of it. So the same repository gets a different agent from the one I work with every day, and a different one again for Claude Code and for Codex.

So I wrote a skill, vendor-agent-harness, that copies the parts of my personal harness that belong to a repository into that repository, for both runtimes. I tested it on scratch fixture repos in three subagent scenarios on 2026-10-02. I haven't run it on a real project yet, so read this as a field report from a test bench, not a migration story.

These are the five rules that came out of it, each with what broke first. I probed the runtime behaviors below on 2026-10-02 with Claude Code 2.1.286 and Codex CLI 0.159.3, so check them on your versions.

1. Make AGENTS.md the real file, and import it from CLAUDE.md

I tested what each runtime loads with a throwaway git repo outside my home directory, with one unique "sentinel" word per mechanism. On the Codex side, codex debug prompt-input prints the model-visible input without making a model call. On the Claude Code side I used claude -p --setting-sources project and asked the model to report its own instructions.

The surprise: Claude Code reads AGENTS.md by itself only when no CLAUDE.md exists. With a CLAUDE.md present and no import, the answer quoted the CLAUDE and RULE sentinels and said AGENTS.md was not in its instructions. So a repo with both files, a shape at least one of my repos has, can have Claude Code quietly ignoring everything in AGENTS.md.

AGENTS.md became the canonical file, and CLAUDE.md must start with @AGENTS.md. Projected rules sit between markers, so a rerun can rewrite its own regions and leave the repository's text alone:

<!-- harness:begin id=git-commits -->

## Git commits

Split changes into conventional commits grouped by concern.

<!-- harness:end -->

2. Write every rule where it loads without trust

Not every repository surface loads on a fresh machine. Instruction files and skills load in both runtimes with no condition. Project config, hooks, agents and MCP servers depend on trust, and the trust rules differ between runtimes and between interactive and headless use.

Codex was strict. A developer_instructions sentinel in .codex/config.toml didn't show up at all until I trusted the project in the user-level config.toml. Passing trust on the command line didn't help: -c 'projects."<path>".trust_level="trusted"' left the sentinel at zero. Per the Codex docs (I didn't probe this one), hooks also need a second review per hook, stored against a hash, so a changed hook needs a new review.

Claude Code's headless mode was looser than my own notes said. My reference file got this wrong, and one of the scenario agents caught it by reading the Claude Code permissions docs ("What runs before you trust a folder"). Per those docs (again not something I probed), an interactive session asks for workspace trust before project hooks load. But claude -p and the SDK never show that dialog: they run settings-file hooks and connect .mcp.json servers anyway.

That is why every rule is first written where it always loads, in AGENTS.md or .agents/skills/. A hook only adds enforcement on top of a sentence that already exists, so a hook always comes with a plain-text version for the untrusted case.

3. Keep one copy of each skill, inside the repository

The two runtimes look for project skills in different places: .claude/skills/ and .agents/skills/.

One commercial Flutter agent toolkit I've used has its installer generate Codex-compatible copies of its skills into ~/.codex/skills/. Its README, as it read before 2026-07-04, gave the reason: Codex doesn't support some tool-facing instructions such as AskUserQuestion. That works on the machine where you ran the installer. But the copies live in a home directory, which is exactly what a fresh machine doesn't have. They also go stale: the toolkit's current README says its update command doesn't rerun the installer, so you rerun it for each CLI yourself.

There was another trap. An empty CODEX_HOME doesn't give you an empty Codex. In my probe, Codex still listed 25 skills, because ~/.agents/skills/ in my home directory is a separate skill root. A test that must see only the repository's skills also needs a different HOME.

The skill's answer comes in three parts.

  • One copy lives in .agents/skills/<name>/, and .claude/skills/<name> is a relative symlink to ../../.agents/skills/<name>. Claude Code listed a skill through that symlink.
  • Hooks get one script in .agents/hooks/, registered in both runtimes. Both pass the shell command in .tool_input.command, and both call the shell tool Bash (Codex also uses apply_patch for file edits).
  • A skill that depends on a helper file brings the helper. Three of that toolkit's skills had failed without the helper scripts the toolkit owns, which is where that rule came from.

Hook registration differs only in how each runtime finds the repo root (Claude Code first, then Codex):

"command": "bash \"${CLAUDE_PROJECT_DIR}/.agents/hooks/<name>.sh\""
"command": "bash \"$(git rev-parse --show-toplevel)/.agents/hooks/<name>.sh\""

4. Don't vendor yourself

This is the one I'm least proud of. In the no-skill baseline, the first scenario's agent did a lot right: AGENTS.md as canonical, the @AGENTS.md import, one skill copy behind a symlink. It also copied me into a repository that the scenario set up as public. Its AGENTS.md gained this line:

Address the operator as Andrew or Dongmin, never 선생님.

And .claude/settings.json set the conversation language to Korean and my output style to fluent-korean. Anyone who clones that repo would get an agent that speaks Korean and avoids an honorific for a person they've never met. (To its credit, the agent flagged this as a trade-off in its report. It shipped it anyway.)

The same run copied hook scripts that still had home-directory paths inside: one fell back to $HOME/.claude, another ran a shellcheck binary from under ${HOME}/.codex. On a fresh machine those point at nothing.

Now every unit of my harness gets one of four classes, and only one class is copied.

  • portable: the rule holds in this repo on any machine. Projected.
  • machine: it describes one machine, such as its disks, tools or paths. Not projected.
  • private: my identity or preferences, such as name, address term, conversation language, output style or account names. Not projected, because one person's preference must not bind every contributor.
  • irrelevant: a stack or workflow the repo doesn't have. Not projected.

Copied text is scanned for home-harness paths, and the check step also fails on a small file of personal patterns (my home paths and identifiers). With the skill loaded, the same scenario had no name, address term, language or output style in the repo, and its report listed them as private.

5. Lock every copy to its source hash

The baseline also left no record of where anything came from: no lock file, no provenance. The agent said it plainly in its report (translated from Korean): there is no mechanism to keep the copies up to date.

So the skill writes an entry for every generated unit, copied file and copied directory into .agents/harness.lock.json, with the source ref and its sha256:

{
  "id": "git-commits",
  "sources": [{ "ref": "claude:CLAUDE.md#git-commits", "sha256": "<from hash>" }],
  "class": "portable",
  "form": "core",
  "targets": ["AGENTS.md#harness:git-commits"]
}

A rerun looks only at what the helper's status command reports, and the run isn't done until check exits 0. In the rerun scenario, status went from CHANGED git-commits to 2 entries, 0 changed or missing, and the diff touched only that one marker region and the lock.

Lessons

  • Test what loads, don't assume it. A sentinel word and a prompt-dump command settled questions that reading alone had left open, and the one thing I didn't probe was the thing my notes got wrong.
  • Trust belongs to the user, not to the repo. Design for the session that trusts nothing, and treat everything else as a bonus.
  • The most dangerous thing to copy is yourself. Your preferences feel like rules because you wrote them as rules.
  • A copy without a hash is a fork you don't know about.

One thing I left out on purpose: the skill stops after showing its plan and changes nothing until I approve. Getting subagents to respect that stop was its own story, which I wrote up in a companion post about what Claude Code subagents don't inherit.

If you run both Claude Code and Codex on one repo, I'd like to know how you keep them in sync, and whether your CLAUDE.md is quietly hiding your AGENTS.md right now. It takes one sentinel word to find out.