One policy for 3 CLIs
Claude Code, Codex and OpenCode all read instruction files, permissions, skills and Model Context Protocol (MCP) servers, the tool servers you built on Day 5. Each one reads them from different files, so keep one source for the policy and check that every CLI loads it.
Study first
- Instruction files: Codex and OpenCode read
AGENTS.md, while Claude Code readsCLAUDE.md(version 2.1 in the reference run). ACLAUDE.mdof one line,@AGENTS.md, importsAGENTS.md, so one file serves all three. Nested files work differently in each CLI. Codex reads a nestedAGENTS.mdas it works in that folder, and Claude Code reads a nestedCLAUDE.mdthe same way. OpenCode 1.19 needs the nested files listed in itsinstructions, and falls back toCLAUDE.mdonly where noAGENTS.mdexists. - Configuration for each CLI: Claude Code reads
.claude/settings.jsonfor permissions and hooks, and.mcp.jsonfor its servers. Codex reads.codex/config.toml, but only for a project that~/.codex/config.tomlmarks as trusted. OpenCode readsopencode.jsonc, with the keysmcp,instructionsandpermission. - Skills: Claude Code reads
.claude/skills/<name>/SKILL.md, and Codex needs a[[skills.config]]entry pointing at each of the same folders. OpenCode 1.19 loads.claude/skillsby itself, on demand, through its skill tool. OpenCode's commands, which a person runs as/name, are plain files in.opencode/commands/with adescriptionin their front matter. - Flags and keys change between versions, so before you rely on one, read
codex exec --helpand the configuration references for Codex and OpenCode.
Build
- Rename the nested
CLAUDE.mdfiles insite/,src/app/api/andcontent/knowledge/toAGENTS.md, and leave aCLAUDE.mdof one line,@AGENTS.md, beside each one. - In
.claude/settings.json, allow the checks and read-only git commands without asking, and deny both reading.env.localandrm -rf. - In
.codex/config.toml, setapproval_policy = "on-request". Add a permission profile under[permissions.<name>]that extends:workspaceand denies**/.envand**/.env.localin its workspace roots, then select it withdefault_permissions. Add the 5 MCP servers from.mcp.jsonas[mcp_servers.<name>]entries, and the 4 skills as[[skills.config]]entries. - In
opencode.jsonc, list the nestedAGENTS.mdfiles andbrain/README.mdunderinstructions. Add the 5 servers undermcp, where a local server takestype: "local"with acommandarray and a remote one takestype: "remote"with aurl. Setpermissionso that edits are allowed, bash commands ask first, and reads of.envfiles are denied. - Write
scripts/sync-commands.mjs, which writes.opencode/commands/<skill>.mdfrom each skill with OpenCode's front matter. Runpnpm sync:commandswhenever a skill changes. - Add a section to the root
AGENTS.mdthat says where each CLI reads the policy from. - Write
scripts/agent-config.test.tsto keep the 3 configurations in step. It checks that.mcp.json,.codex/config.tomlandopencode.jsoncname the same servers, and that every nestedAGENTS.mdis an OpenCode instruction with aCLAUDE.mdbeside it. It also checks that every skill has a Codex entry, and that its OpenCode command equals what the sync script writes.pnpm test scriptsruns the test on its own, andpnpm testruns it with the rest.
Done when
pnpm test scriptspasses.- Each CLI names its instruction files, servers and skills when you ask it. Ask Claude Code with
claude -p --allowedTools "Read,Glob,Grep", which leaves it no write tool, and ask Codex withcodex exec --sandbox read-only. Ask OpenCode withopencode run, and deny the shell for that run withOPENCODE_CONFIG_CONTENT='{"permission":{"bash":"deny"}}', because underbash: aska non-interactive run ends at the first command it refuses. - An env file is out of reach. Asked to read one,
opencode runanswers with the rule that stops it, andcodex sandbox --permission-profile <name> -C <repo> -- cat .env.localfails with "Operation not permitted" while.env.examplestill reads.


Technical notes
- Codex ignores some keys in a project config, such as
model_provider, so keep those in your user config. A permission profile needs Codex 0.138 or later, and is in beta. It applies only when no loaded config sets the oldersandbox_mode, your own user config included, and passing--sandboxon the command line brings the older settings back. To test the profile on its own, usecodex sandbox --permission-profile. - Claude Code likewise ignores a project's
permissions.allowentries until you have trusted the workspace once in an interactive session, and it says so at the start of a non-interactive run. - The sync script writes OpenCode's commands from the skills instead of linking to them, so each command carries only the front matter OpenCode documents.
pnpm sync:commandsrewrites the directory, and the test compares it with the skills. - OpenCode's glob tool does not match files under dot-directories, as the screenshot shows with 0 matches for
.opencode/commands/*. Its read tool lists a directory, which is how the probe, the question you ask each CLI, reaches the commands. - In a non-interactive OpenCode run, a permission that would ask is refused, and the run ends there. A rule that denies instead returns to the model as an error it can work around, which is the kind a probe needs.
- Never put a secret in a config file. The local MCP servers, which run over stdio, read
.env.localthrough--env-file-if-exists. Each CLI has its own rule against reading that file: Claude Code'sdenylist, OpenCode'sreadpattern and Codex's profile.