Apply to my task
arXiv:2610.16870 ★ 92 Oct 12, 2026

Checks "by the Spec, Not by the Code": Why Agents Write Tests to Fit the Existing Implementation and How to Catch It

Key result

Finding: coding agents (Codex, Claude Code, OpenCode) write tests based on the code in front of them, not on the spec. The method gets you tests that catch bugs without rejecting a working rewrite of the code. The trick: don't let the code act as the template, name the spec as the standard, and make the agent strike out everything the spec is silent about. Otherwise false alarms creep into the tests: they demand an element order or error text the spec never mentions. The best agent in the study builds correct checks on only 63% of tasks.

01

TL;DR

The study looks at how coding agents (Codex, Claude Code, OpenCode) write checks from a written spec. The agent gets the spec, the repository, and a finished implementation. It has to write a validation harness: a test-scenario generator (which arguments to call the code with) and the checks themselves (what must hold). The harness is tested on three implementations: the reference one, one with an injected violation, and a valid alternative. The alternative is written differently but doesn't violate the spec.

The key finding: agents write checks that copy the implementation's behavior, not the spec's requirements. So the harness catches bugs but also rejects working code: it demands a specific element order, an exact error message, or a side effect the spec never mentions. The best result is fully correct checks on only about 63% of tasks. Agents detect violations more often than they write correct checks: a gap of 11–22 points eaten up by false alarms. Show an agent an implementation with a bug, and it loses 19–31 points of correctness: its checks either accept the bug or reject correct code. The agent "grounds" its checks in whatever code is in front of it.

The paper's evaluation method carries over into practice as a simple scheme: test the agent's tests not only on "catches the bug" but also on "lets a correct rewrite through." The deciding quality criterion for checks is "no more and no less than the spec requires". The takeaway for your workflow: whenever possible, don't give the agent the code when it's writing checks from the spec. If the code has to be in context, explicitly tell it to treat the spec, not the code, as the standard.

Want this for your own case? Apply this study to your task: AI adapts its method to what you are working on and writes the prompt.

02

Blueprint for Checking an Agent's Tests (How the Paper Evaluates)

STEP 1: Spec → split into atomic requirements (one requirement = one violation)
STEP 2: The agent gets the spec + repository + implementation → writes a harness:
        a) a scenario generator (which arguments to feed in)
        b) checks (what must hold), not tied to any specific scenario
STEP 3: Run against three implementations:
        • reference              → checks MUST accept
        • valid alternative (different code, doesn't violate the spec) → MUST accept
        • with a spec violation  → MUST reject
STEP 4: Correct = violation rejected AND everything correct accepted
        False alarm = correct code rejected
03

Example Application

Task: A Python online store, like a Shopify shop or an Etsy-style marketplace. There's a function that calculates the cart total with a promo code. Spec: "A promo-code discount cannot exceed 30% of the cart total. The discount does not apply to items marked "clearance". The total is rounded down to the whole dollar." The code is already written. You want the agent to write checks that will catch future bugs during refactoring but won't break on harmless changes like a different order of line items on the receipt.

Prompt:

You are writing checks for the apply_promo function in cart/pricing.py.

The standard of correctness is the spec below, not the current code. The code may not match the spec.

Spec:
1. The promo-code discount does not exceed 30% of the cart total.
2. The discount does not apply to items marked "clearance".
3. The final total is rounded down to the whole dollar.

For EACH spec item separately:
A) Scenario generator: a list of different calls (empty cart, single item,
   clearance only, a 50% promo code, cents in the total, a mix of regular and clearance items).
   Give concrete arguments for each.
B) Checks: assertions about the result that follow ONLY from the spec text.
   A check is not tied to a specific scenario and works for any call.

Rules for checks:
- Check no more and no less than the spec requires.
- Don't check what the spec is silent about: the order of items in the output, message text,
  internal field names, the number of helper-function calls.
- If you're unsure whether an assertion follows from the spec, drop it and add it to the list
  "Not checking, because the spec is silent".

At the end, for each check answer: "What correct code could it
wrongly reject?" If such code is easy to imagine, loosen the check.

Result: For each spec item, the agent produces a set of scenarios and checks that aren't tied to a specific call. In a separate list it names what it deliberately isn't checking. At the end comes a self-critique of each check: what correct variant of the code it might reject. Most likely the checks won't be hard-wired to line-item order or error text. But this is no full guarantee: even the best agents in the study missed on a third of tasks. So you still need to run the checks against a known-good rewrite of the code and a known-broken one.

04

Why It Works

The weakness. If finished code sits in the context, the model treats it as the template. A test built from such checks turns into a "retelling of what the code does today." It sees any difference from the code as an error, even one the spec allows. If the code has a bug, the check locks in the bug too. The experiment showed this: when given an implementation with a violation, every agent lost 19–31 points of correctness.

The strength. The model can read written requirements and derive assertions about the result from them. It's also good at inventing varied inputs. It does manage to find violations when there's something to check: agents' detection rate is noticeably higher than their correctness. The weak spot is in the checks, not in the number of scenarios.

What the approach does. It separates the sources: the spec is the standard, the code is reference only. It forces a separate answer to "what does this spec actually require?" and striking out anything extra. An explicit "not checking, because the spec is silent" list acts as a barrier against the habit of listing everything visible in the code.

Control levers: - Remove the implementation from context entirely → the strongest lever against copying the code (but then the agent can't run scenarios). - Strictness of striking out ("when in doubt, drop it") → loosen it if bugs slip through; tighten it if the agent rejects correct code. - Splitting the spec into atomic items → one requirement per check, so it's easier to see exactly what broke. - The final "what correct code are you rejecting" question → can be asked in a separate pass by a different model.

05

Prompt Template

You are writing checks for {function_or_module} in {file}.

The standard of correctness is the spec below, not the current code.
The code may not match the spec; don't copy its behavior.

Spec:
{numbered_list_of_requirements}

For EACH requirement separately:
A) Scenario generator: a set of concrete calls (arguments), including
   edge cases: {which_edge_cases}.
B) Checks: assertions about the result that follow ONLY from the spec text.
   A check is not tied to a specific scenario and works for any call.

Rules:
- Check no more and no less than the spec requires.
- Don't check what the spec is silent about: {what_the_spec_does_not_fix}.
- In doubt, drop the assertion and add it to "Not checking, because the spec is silent".

At the end, for each check answer: what correct code could it wrongly
reject? If that's easy to imagine, loosen the check.

What to fill in: - {numbered_list_of_requirements} — the spec split into separate items. Each requirement should be breakable independently of the others. - {which_edge_cases} — empty data, limits, cents, non-standard types. - {what_the_spec_does_not_fix} — element order, error messages, internal fields, side effects. You know these items better than the agent does.

🚀 Quick start — paste into a chat:

Here is a prompt template for checks "by the spec, not by the code". Adapt it to my task: {describe_the_function_and_spec}.
Ask me questions to fill in the fields.

[paste the template above]

The LLM will ask what requirements are in the spec, which edge cases matter, and what the spec deliberately leaves open. That's what makes the method work: without a clear list of what the spec is silent about, the agent can't cut the extra checks.

06

Limitations

⚠️ The paper is a benchmark, not a ready-made method. The authors measured where agents go wrong but didn't test how much a prompt like the template above helps. The template is built from the paper's task framing and main findings. Its gain wasn't measured in the paper.

⚠️ The best result is still weak. No agent built a correct harness on more than 63% of tasks. Run the checks against correct and broken versions of the code yourself.

⚠️ You need a clear specification. The benchmark tasks are "atomic" requirements pulled from a good spec. If the spec is vague ("runs fast," "user-friendly"), the method won't work: there's nothing to strike out and nothing to check against.

⚠️ Python and synthetic violations only. The bugs were injected by another model, not a real dev team. Tested on open-source Python repositories.

⚠️ Keeping code out of context has a price. If you remove the implementation, the agent can't run scenarios. The balance: keep the code, but firmly order the agent to treat the spec as the standard.

07

How They Studied It

The researchers took 30 real functions from open-source Python projects (13 repositories) and split each spec into small requirements. That gave 291 tasks. For each, the requirement was violated: Claude Opus 5 injected a bug into the code that the original tests didn't catch. A second model from a different family, GPT-5.6-sol, confirmed by a three-answer vote that it was a real violation, not a harmless difference. Failed variants were discarded. Bugs from neighboring requirements became the "valid alternatives": correct for the current spec item, but not for their own. Two authors manually checked 50 tasks and agreed with each other 94.7% of the time; they matched the pipeline's decision 98% of the time.

Then four agents (Codex with GPT-5.6-sol, Claude Code with Opus 5, OpenCode with DeepSeek-V4-Pro and GLM-5.3) wrote harnesses with 80 model calls per task. Checks counted as correct if the bug was rejected and everything correct was accepted. Correctness came out at 45–63%, detection at 65–82%, false alarms at 15–27%. The rankings were the surprise: GPT-5.6-sol detected violations 8 points more often than GLM-5.3, but it also raised 8 points more false alarms, and their correctness ended up nearly equal. False alarms made up 42–60% of all failures.

To figure out whether the scenarios or the checks were to blame, the researchers gave the agents "perfect" scenarios guaranteed to expose the bug. For three of the four agents, most missed violations remained: the checks still accepted the bug. So the bottleneck is in the checks themselves. They also showed agents the buggy implementation instead of the reference one. All four lost 19–31 points of correctness and started rejecting the reference implementation they'd never seen on 12–41% of tasks: the agent tailored its checks to what it saw.

08

Adaptations and Extrapolations

🔧 Technique: add an "alternative implementation" → catch false alarms

After the agent writes the checks, ask it (or another agent) to rewrite the function differently: different order, different internal structures, different error messages, but the same behavior per the spec. Run the checks on it. If something fails, that's a false alarm: the check demands more than the spec says. The idea comes from the benchmark's design, but the paper uses this procedure for evaluation, not as a practitioner's technique.

🔧 Technique: break the code on purpose → catch missed bugs

Ask another agent: "Introduce exactly one violation of spec item {N} into the code, one that isn't visible from reading the code but shows up when it runs." If the checks let the violation through, they're weak.

Extrapolation: two passes with split roles.

Pass 1 (agent A, WITHOUT access to the code):
Here is the spec: {spec}. Write checks from the spec text only. You are not shown the code.

Pass 2 (agent B, WITH access to the code):
Here is the spec, here is the code, and here are agent A's checks. Run the checks on the code.
If a check fails on correct code, use the spec to decide: is the bug in the code,
or does the check demand more than the spec? Don't change a check if the spec supports it.

This is my combination, not from the paper: it applies the "agent copies the code" finding through context separation.

09

Resources

  • SpecGuardBench: Can Coding Agents Construct Reliable Validation Harnesses from Natural Language Specifications? (Preprint)
  • Code and data: github.com/coxhin/SpecGuardBench, huggingface.co/datasets/coxhin/SpecGuardBench
  • Authors: Zhiqing Zhong, Mingyu Chen, Aoyang Fang, Yuzhong Zhang, Haoye Tian, Lionel Briand, Boxi Yu, Pinjia He
  • Affiliations: The Chinese University of Hong Kong, Shenzhen; Aalto University; Lero (Research Ireland Centre for Software); University of Limerick; University of Ottawa
  • Dataset basis: FeatureBench (Zhou et al., 2026)

Research digest

Key result

Finding: coding agents (Codex, Claude Code, OpenCode) write tests based on the code in front of them, not on the spec. The method gets you tests that catch bugs without rejecting a working rewrite of the code. The trick: don't let the code act as the template, name the spec as the standard, and make the agent strike out everything the spec is silent about. Otherwise false alarms creep into the tests: they demand an element order or error text the spec never mentions. The best agent in the study builds correct checks on only 63% of tasks.

Principle

A good test accepts any implementation that doesn't violate the spec. That's why the paper runs every check against three versions of the code. The reference and the valid alternative (written differently, but not violating the spec) must both pass. The version with a violation must fail. A test is good when it checks no more and no less than the spec requires. It's like a home walkthrough against the contract. You compare with the contract, not with what got built. If the contract says nothing about door color, a brown door is no reason to refuse the handover. The agent, though, often writes: "the doors must be brown, because that's what's there now."

Why it works

The model treats finished code as the template. The test turns into a retelling of what the code does today. It sees any difference from the code as an error, even one the spec allows. If the code has a bug, the test locks in the bug too. The experiment confirmed it: when agents were shown an implementation with a violation, the correctness of their checks dropped by 19–31 points. The agent reads the spec no worse than the code, but goes with whatever is more visible: the code in front of it. Hence the gap: agents detect violations 11–22 points more often than they build correct checks. False alarms eat up the difference. The weak spot isn't the number of scenarios but the assertions themselves. So the prompt does three things. It names the spec as the standard. It requires a "not checking, because the spec is silent" list. And at the end it asks the agent to find correct code that the test might reject.

When to apply

Refactoring and regression tests → when an agent writes checks for a function with a clear spec made of separate items, especially if tests fail on harmless changes after a rewrite (item order, message text). NOT suitable for vague specs like "runs fast" and "user-friendly": there's nothing to strike out and nothing to check against. Important: the paper offers a benchmark, not a ready-made method. The prompt is assembled from its findings. The authors didn't measure its gain. Tested only on Python.

Mini recipe

1. Slice the spec into items: one requirement, one violation. Number them. 2. Name the standard: "The standard of correctness is the spec, not the current code. The code may have bugs." 3. Ask for scenarios and checks separately: first a list of calls with edge cases, then assertions that hold for any call. 4. Forbid the extras: list what the spec doesn't pin down. For example, element order, error messages, internal fields. 5. Grant permission to drop: "In doubt, strike it out and add it to the 'Not checking, because the spec is silent' list." 6. Request a self-critique: for each check, "what correct code could it wrongly reject?" For reliability, have a different model do this. 7. Run it for real: take a known-good rewrite and a known-broken one. The first must pass, the second must fail. If you can, don't show the agent the code at all, but then it can't run the scenarios.

Examples

[BAD]: `Write tests for the apply_promo function in cart/pricing.py` [GOOD]: `You are writing checks for apply_promo in cart/pricing.py. The standard of correctness is the spec below, not the current code. The code may not match the spec. Spec: 1) the promo-code discount is no more than 30% of the cart total; 2) the discount does not apply to items marked "clearance"; 3) the total is rounded down to the whole dollar. For each item separately: A) a list of calls (empty cart, clearance only, a 50% promo code, cents in the total); B) assertions that follow only from the spec text. Don't check item order, message text, or internal fields. In doubt, drop it and add it to "Not checking, because the spec is silent". At the end, for each check answer: what correct code could it wrongly reject?` The difference: in the bad case, the agent transcribes the code's behavior into the tests. It locks in the order of items on the receipt and the exact error text. Any normal rewrite of the code will break such tests. In the good case, the tests check only the three spec rules, and the agent moves everything extra into a separate list.

In plain words

AI agents don't write tests the way naive team leads imagine. Feed a model a written spec and a finished repo, and it ignores the requirements and takes the path of least resistance. The model produces a dumb snapshot of the code's current behavior. Instead of checking the business logic, you get a mirror of the implementation: if the source has a critical bug, the agent will lovingly write a check requiring that bug to keep working forever.

It's like hiring an independent inspector to check a house against the blueprints, and instead of reading the plans he measures a crooked wall and writes it into the building standard. Technically, an inspection happened, but it's worthless: if the crew forgot a load-bearing beam, the inspector decides that was the plan, and then flunks a perfectly straight wall built by another crew. That's not quality control, it's legalizing someone else's screwups.

The SpecGuardBench benchmark laid this mess bare on models at the level of Codex and Claude Code. The agents were told to write scenarios and checks, and then their tests were run against the reference code, a buggy version, and a valid alternative. The result was a predictable flop: on defective code, the agents lost 19 to 31 points of correctness. The models instantly trusted the broken implementation and welded the bug into the tests.

They tested it on online-store cart logic, but the principle is universal. The trap is waiting everywhere, from microservices to fintech, wherever people bolt on AI for auto-generating regression tests. The generated checks will break on the first normal refactor — say, if you just change the order of fields in the response, even though the spec isn't violated. Illusory reliability turns into a world of pain on the very first edit.

The main takeaway: never give a coding agent the finished implementation if you want honest test scenarios. Make the model write tests blind, purely from the spec text. Otherwise the model will just pour your tech debt into concrete in your repo. Want a robust pipeline? Keep the requirements isolated from the code before the AI piles up useless hacks.

AI lab assistant

Apply this study to your task

Describe what you're working on. AI takes the method and the measured results from this paper and adapts them to your case, or writes a ready prompt built on them.

Based on Checks "by the Spec, Not by the Code": Why Agents Write Tests to Fit the Existing Implementation and How to Catch It arXiv:2610.16870

Sign in to apply this study New accounts get 50 free credits.