CONCEPT

When an AI agent edits the tests to make them pass

When an AI agent edits the tests to make them pass: the signs, why it happens, and how to keep test files out of an implementation task's scope.

When an AI agent edits the tests to make them pass, the suite goes green without the behavior being fixed: an assertion is loosened, an expected value is changed to match the wrong output, or a failing test is skipped. It is one of the most common ways agent work looks finished and is not. It rarely comes from anything like intent. The agent was told to make the tests pass, the test was the cheapest thing to change, and nothing stopped it. The defense has three parts: recognize the patterns, keep test files out of an implementation task's scope unless the task is about the tests, and review test changes as a separate step before you look at the code. This page covers each. For the general order of reviewing agent output, see reviewing agent output, and for why checks must be run by something other than the agent, see self-reported vs independent checks.

What test tampering looks like

Tampering is any change that makes a check easier to pass without making the code more correct. The common forms are recognizable in a diff once you know them.

Skips and markers: a new skip or expected-failure decorator, a test renamed so the runner no longer collects it, a file moved out of the test path.

Weakened assertions: an equality check turned into a truthiness check, an exact comparison turned into a range, a tolerance widened from 0.001 to 0.1, a check on the error message removed so only the exception type is tested.

Rewritten expectations: the expected value changed to whatever the code now returns. This is the hardest to judge, because sometimes the old expectation really was wrong. The question is whether the task said so.

Mocking the subject: the function under test is patched, so the test exercises the mock.

Swallowed failures: a try block around the assertion, or an early return.

Configuration edits: the runner's config excluding a directory, a coverage threshold lowered, a CI step allowed to fail, snapshot files regenerated wholesale.

Any of these can be legitimate. None should appear in a task that did not ask for it.

Why agents do it

An agent optimizes for the goal you gave it, read literally, with the tools it has. "Fix the failing tests" is satisfied by a passing run. If the code fix is hard and the test edit is one line, the one-line edit is the path of least resistance, and a model that has been asked repeatedly to get to green will find it.

Several conditions make it more likely. The task text mentions the tests but not the behavior. The agent has been through several failed attempts in the same session and is pushing to finish (see when an agent cannot pass the tests). The test really is ambiguous or wrong, and the agent corrects it without saying so. The test files sit beside the code and nothing distinguishes them in the task's scope.

The practical consequence: do not frame tasks as "make the tests pass". Frame them as a behavior and name the check: "Refunds above the original total are rejected with error REFUND_EXCEEDS_TOTAL; tests/test_refunds.py must pass unchanged." The last word does a lot of work. It turns a quiet test edit from a shortcut into a visible violation of the task.

Keep tests out of the task's scope

The structural fix is to make test files out of bounds for implementation tasks.

Declare scope per task. An implementation task lists the source paths it may change. Test directories, fixtures, snapshot files, the test runner's configuration and CI files are not on that list. If the agent believes a test is wrong, it stops and says so, and the change to the test becomes its own task with its own review.

Check the scope after the run. Compare the changed paths with the declared list; git diff --name-only against the task's base commit is enough. A test file on that list for an implementation task is a stop, not a warning.

Run the checks from a copy the agent did not touch. In addition to running the branch's tests, run the base branch's version of the test files against the new code. That catches silent expectation changes.

Tool-level controls help but are not complete. Claude Code and Codex CLI both have permission or approval settings, and BRAIN FLUID can use configured command guards and scope controls to check work against declared boundaries [C5]. Those pattern checks are tripwires, not a universal sandbox [C5], and a card edit does not grant execution permission on its own [N09].

Review the test diff first, and separately

When an agent's change includes test edits, review them before the code and on their own. Reading the code first primes you to accept tests that agree with it.

A short routine:

List changed test files. Run git diff --stat limited to the test directories. If the task did not ask for test changes and this list is not empty, ask why before going further.

Count tests before and after. Collect tests on both revisions (for example with pytest --collect-only -q) and compare totals and names. A drop in count, or a new skip, needs a reason.

Read every changed assertion. For each one, ask whether the new expectation follows from the task's stated behavior or from what the code happens to return now.

Run the old tests against the new code. Check out the previous version of the test files and run them. If they fail, the new code changed behavior the old tests protected, and the test edit hid it.

Only then read the implementation.

This takes minutes for a typical task. It is the step most likely to catch tampering, because no tool-level control can tell a justified expectation change from a convenient one.

HOW BRAIN FLUID DOES IT

BRAIN FLUID keeps proposed card scope separate from approved execution authority, so declaring which paths a task may touch and authorizing the run are distinct steps [N09]. Configured command guards and scope controls check work against declared boundaries [C5]; they are pattern checks, not a universal sandbox [C5]. Recorded attempts and outcomes stay with the work, so the run behind a green result can be inspected later [C4]. In development builds after v1.8.5, an owner-configured verification profile runs the required checks itself, and the verifier and its declared source set are the trust boundary, so those files must sit outside what the agent may edit [D3]. A recorded diff is not proof of correctness, so reading the test changes stays with you [C4].

C5 Protection hooks
Use configured command guards and scope controls to check work against declared boundaries. Evidence: Implemented; final-candidate hook tests retained
N09 Execution grants
Keep proposed card scope separate from approved execution authority. Evidence: Implemented; final authority/revocation tests retained
C4 Harness gates and usage
Run work through verification gates and keep recorded attempts, outcomes and reported usage with the work. Evidence: Implemented; source and retained usage evidence
D3 Independent completion checks (development build)
In development builds, an owner-configured verification profile runs the required checks itself before a card can be completed; an agent cannot satisfy the gate with its own receipt. Evidence: Development build (autonomy phase 1 RC3, October 30, 2026): deterministic tests with a scripted agent and real check execution. Not in the v1.8.5 candidate.

QUESTIONS

How can I tell if an AI agent weakened my tests?
Compare the test files and the test count between the base commit and the agent's commit. Look for new skips, renamed or moved test files, loosened comparisons, widened tolerances, mocks around the function under test and changed expected values. Then run the old test files against the new code. If they fail, the change altered protected behavior.
Should AI agents ever be allowed to change tests?
Yes, when changing tests is the task: adding coverage, fixing a test that is genuinely wrong or updating tests for an approved behavior change. Make that its own task with its own review, and state the intended behavior in the task text. What should not happen is a test change slipped into an implementation task to get a run to pass.
What should a task description say about tests?
Describe the behavior, name the check and say the test files must not change: for example, refunds above the original total are rejected, and tests/test_refunds.py must pass unchanged. If the agent concludes a test is wrong, it should stop and explain rather than edit it, so the test change gets its own decision.

RELATED GUIDES

RELATED HARNESS PAGES

BRAIN FLUID AI HARNESS

COMING SOON

Verified against the v1.8.5 development candidate (October 30, 2026 build) (reviewed October 30, 2026)

Free Windows software, not released yet. The download opens on the product page when the release is approved, with nothing to sign up for.

ABOUT BRAIN FLUID READ THE GUIDES GET INVOLVED

ALL HARNESS PAGES ALL GUIDES