Codex vs. Claude Code at Liar's Dice: the Winning Bluff Was the Truth A developer pitted OpenAI's Codex CLI against Anthropic's Claude Code in a Liar's Dice tournament, with Claude Code winning all three best-of-3 series. The engineer built a tamper-proof MCP-based setup to ensure fair play, and found that Claude's challenge calls hit 8 out of 11, while Codex's hit 4 out of 26. The developer also discovered that a model's CLI behavior, not the model itself, caused an anomaly, highlighting the importance of tool design. One authoritative engine, two seat-locked MCP servers, three best-of-threes, and a 3-millisecond whodunit The matches are real: Codex CLI gpt-5.6-sol against Claude Code Claude Opus 5 , both playing through the same rules engine. Every number below was recomputed from the raw run.json and both session logs, and every game replays deterministically from its seed. Quotes from the agents are verbatim from decision-time records. None of this is a general model ranking. I wired Codex CLI and Claude Code into the same Liar's Dice engine over MCP and had them play three best-of-3 series. Claude won all three, 2–0 each time. Its challenge calls hit 8 out of 11; Codex's hit 4 out of 26. The score takes two sentences. The parts worth writing down took longer: how to build a table that two closed-source agents can't cheat at, two numbers that surprised me, and an incident where I almost blamed a model for something its CLI did. Liar's Dice in sixty seconds: five dice each, and you only see your own. Players alternate bids of the form "there are at least N dice showing X across the whole table." On your turn you either raise the bid or challenge it. On a challenge everyone reveals; if the bid stands, the challenger loses a die, otherwise the bidder does. Run out of dice and you lose the match. Ones are wild by default. The rules are the easy part. The hard part is making the result trustworthy. Codex and Claude Code ship with their own system prompts and tool loops, so the referee has to guarantee three things by construction: neither side can see the other's dice, the referee has no side channel that favors anyone, and the "what it was thinking" quotes you read afterward were actually written at decision time. The setup is one in-process rules engine behind a localhost-only HTTP coordinator, with two stdio MCP servers doing nothing but forwarding: Codex CLI gpt-5.6-sol Claude Code Opus 5 | stdio MCP | stdio MCP v v seat-mcp A --token A--+ +--token B-- seat-mcp B | | v v +------------------------+ | coordinator @127.0.0.1 | | - createMatch engine | | - stateId concurrency | | - run.json audit log | +-----------+------------+ | SSE v /spectate The load-bearing decisions: seat parameter. A client that wanted to impersonate its opponent would have nowhere to type that. observe projection, and that JSON has no field for opponent dice. Removing the field beats writing "please don't peek" in a prompt. stateId ; submit against a stale one and you get a 409. Concurrent peeks produce a handful of these per run 0–7 , all preserved in the rejection log. belief and may include one public say . The belief commits atomically with the action; the opponent never sees it and nobody can rewrite it afterward. Every quote below comes from there. assert: "current bid is false" or they're rejected. This came out of an earlier replay study: for one model, turning "challenge" from a bare verb into an assertion it has to type out cut guaranteed-loss challenges from 23% to 2%. Tool schemas change behavior, and that one is measured.Both seats receive word-for-word identical instructions and the identical task prompt, each running in an isolated temp directory with user config ignored. One session plays the entire series, so cross-game memory is part of what's being tested. What I can't control is each CLI's internal prompting and scheduling, which is why the contestants are, and stay, two systems: Codex+Sol and Claude Code+Opus. Every claim here is scoped to that. After the runs I did four checks: claude-opus-5 ; tokens, cost, and cache reads for both sides are archived.The workflow: one continuous-session BO3; the mirror run just flips --codex-seat node scripts/mcp/run-showdown.mjs --best-of 3 --seed 73019426 \ --codex-seat A --codex-model gpt-5.6-sol --claude-model opus verify later: replay all events from seed + actions, diff against archive node scripts/mcp/replay-showdown.mjs docs/showdown/