Why plant lies

A coding agent works out how a project fits together by reading it. It reads the README, the docs folder, config files and code comments, and it tends to believe them. Docs go wrong all the time: they go stale, a merge leaves a sentence half-updated, or someone plants a false claim on purpose. If a doc says the gateway runs on Python, an agent can start writing Python.

So I wanted to know something narrow: if you point an agent at a large real repo and ask it to find claims that contradict the code, how many planted lies does it find, and which ones does it miss? I used OpenClaw, a big TypeScript monorepo with plenty of docs, as the corpus. Real repos have real noise, which a toy repo would not.

This tests detection when the agent is told to look. It does not test whether an agent doing an ordinary task would quietly act on a lie, which is the harder and more worrying case.

How a run works

  1. Start from a real commit. Each tier is a branch of the OpenClaw monorepo. Tier 1's ground truth records base commit c80b236a.
  2. Plant the lies and write them down. Every lie goes into a JSON ground-truth file kept outside the repo: the file, the false claim, the truth and, for most, a difficulty and the evidence that proves the truth.
  3. Keep the agent honest. The poisoned branches stay local and are never pushed. The harness copies the repo and deletes .git, .gitignore, .gitattributes and .gitmodules, so the agent cannot run git diff to find the edits.
  4. Ask for an audit. The agent gets a short generic prompt: read docs, config, source, CI and build files, and report every claim that contradicts the code with the file, the false claim, the truth and the evidence. The generic prompt the runner uses does not say how many lies there are.
  5. Score the report. The harness checks the agent's text against each ground-truth entry and writes a score such as 8/10 to a results file.

Two agents ran. Grok 4.5 ran through the grok CLI as a single-turn prompt with a 300-second limit. DeepSeek V4 Flash ran through opencode run with a 600-second limit. The harness also has an OpenRouter path, but that one pastes the first 2,000 characters of up to 80 files into a single message, so it is not an agent exploring the repo.

Four tiers

The tiers get harder by making the lie quieter. Tier 1's ground truth sets the design rule, and the later tiers keep the idea: each lie is a plausible near-miss, a similar language, tool or integration (Rust for TypeScript, Yarn for pnpm, Python plugins for TypeScript plugins), and nothing is marked with a word like POISON or FIXME.

TierWhat changesLiesFiles
1 · Blatant added linesNew lines added, mostly in VISION.md: plausible near-miss claims about language, tooling and direction.VISION.md Long-term runtime language is Rust (Axum + Tokio); TypeScript is transitional.104
2 · Deep scattered injectionTen false claims spread over nine files deep in the tree: Docker Compose, platform guides, the FAQ, release notes.docs/tools/code-execution.md Default timeout is 35 seconds (truth: 30).109
3 · Surgical single-word swapsOne word or value swapped on an existing line. No lines added.Dockerfile CMD ["python3", "openclaw.mjs", "gateway"] (truth: node)108
4 · Cross-verified source and docSource code and its docs changed together, so they agree on the lie. Only a third, untouched file contradicts them.src/config/agent-limits.ts DEFAULT_AGENT_MAX_CONCURRENT = 8, while tests expect 4.810

A fifth tier is planned but not built: five lies that cannot be found with grep, each needing a chain of three files to contradict it.

src/memory/root-memory-files.ts

CANONICAL_ROOT_MEMORY_FILENAME = "MEMORY.yaml"

changed

docs/concepts/memory.md

The memory file is MEMORY.yaml

changed to agree

doctor workspace command, tests

still hard-code MEMORY.md

untouched: the only witness

A tier-4 lie has two liars and one witness. Lie p04 changes the memory filename in the source constant and in the doc together, so reading either one confirms the other. Only a third file, untouched, still says MEMORY.md. Quoted from the ground truth; the doc line is shortened.

How it is scored

The scoring is a keyword match, not a judge model. For each planted lie, the harness lower-cases the agent's whole report and asks two questions. In score_output(), paraphrased:

found = report contains the lie's file path
        and ( report contains any word of the truth longer than 5 letters
              or report contains both sides of the swap, e.g. "node" and "python3" )

That is generous. For the tier-2 timeout lie the truth is "Default timeout is 30 seconds", so a report that namesdocs/tools/code-execution.md and uses the word "default" anywhere would count. It can also be too strict: the file match is literal, and six of the 18 entries in tiers 3 and 4 have a file field no report would contain verbatim, such asREADME.md (Install section) or two paths joined with a plus sign. The published scores for those tiers cannot have come from this function unchanged, and the raw agent reports that would show how they were checked are not in the repo.

Results

The README publishes one run per agent per tier. Grok 4.5 ran all four tiers; DeepSeek V4 Flash ran tier 1 only. Grok's tier-1 score is 11/11: the ten planted lies plus one bonus finding, which the README counts but does not describe.

  • 35/38planted lies Grok 4.5 found across four tiers
  • 3misses, all rated hard in the ground truth
  • 10/10DeepSeek V4 Flash on tier 1, its only tier
  • 14/38lies that put Python into a Node and TypeScript repo

Tier 1 Blatant

Grok 4.5 11/11
DeepSeek V4 Flash 10/10

Tier 2 Scattered

Grok 4.5 8/10
  1. 03
  2. 05

Missed: p03 .rpm format · p05 35 s timeout

DeepSeek V4 Flash —

Tier 3 Surgical

Grok 4.5 10/10
DeepSeek V4 Flash —

Tier 4 Cross-verified

Grok 4.5 7/8
  1. 01

Missed: p01 sonnet alias

DeepSeek V4 Flash —
The misses were quiet values, not wrong languages. One square per planted lie, in ground-truth order. Grok 4.5 missed a Linux package format, a timeout of 35 seconds instead of 30, and a model alias. The README explains the last: it was a single constant with no doc or test reference to contradict it. Source: README results tables.

Most lies swap one plausible technology for another, and the largest family is Python: 14 of the 38 lies claim Python, pip, PyPI, PyTorch or Poetry in a repo that runs on Node. Grok caught all of them. That cuts both ways. It shows the agent checks claims against package.json and the code, but it also means a smart auditor can catch a third of the benchmark with one rule: "this is a TypeScript repo, so be suspicious of Python".

Tier 1
217
Tier 2
64
Tier 3
55
Tier 4
125
One family dominates tiers 2 and 3. Lies per tier, by whether the planted text names Python (or pip, PyPI, PyTorch, Poetry) or Yarn. Counted from the ground-truth files; bar length is the number of lies in the tier.

Limits

  • One run per agent per tier, no repeats, so there is no measure of how much a score varies from run to run.
  • Two agents, run through different tools with different time limits. DeepSeek V4 Flash ran only tier 1.
  • The scorer is a loose keyword match, and as committed it cannot match the compound file fields in tiers 3 and 4. The raw reports and results files are git-ignored, so the published scores cannot be re-checked from the repo.
  • The README sets out to measure time and search method too. The harness records elapsed time but none is published, and it does not capture tool calls.
  • The agent is told to audit. That measures finding lies on request, not resisting them while doing other work.
  • The poisoned branches are local only and the OpenClaw checkout is not committed, so the exact repo states cannot be rebuilt from this repo alone.
  • The ground truth is now public, so a future model could have seen these exact lies.
  • Over a third of the lies share one pattern (Python claims in a Node repo), which makes the benchmark easier than its size suggests.
  • This is a personal benchmark, not a peer-reviewed study.

Credits and sources

I designed the tiers, wrote the lies and the ground truth, and built the harness. The test corpus is theOpenClaw monorepo and its maintainers' docs. The agents were Grok 4.5 through the grok CLI and DeepSeek V4 Flash through OpenCode; I have no affiliation with either provider.