{"slug": "agent-s-tests-passed-prove-they-could-have-failed", "title": "Agent's tests passed. Prove they could have failed", "summary": "A new open-source tool, mustfail 0.1.0, reverts the source files a coding agent changed while keeping the agent's tests exactly as written, then reruns the checks to prove a passing suite could actually have failed. The tool, installable via pipx or pip and requiring Python 3.8 or newer plus git, runs a positive control on the work as written and a negative control with changed source files reverted to the baseline commit, returning exit code 3 when every check passed but the reverted-source control was not detected. mustfail reports zero runtime dependencies and isolates runs in a throwaway git worktree so the user's own working tree is never touched.", "body_md": "**Your agent's tests passed. Prove they could have failed.**\n\n`mustfail` reverts the source files your coding agent just changed, keeps the\ntests it just wrote exactly as written, and runs the checks again. A check that\nstill passes did not test the work.\n\nZero runtime dependencies. Python 3.8 or newer, and git.\n\n```\nmustfail 0.1.0    HEAD d28ac3e (dirty)    baseline d28ac3e\n  auto: working tree is dirty, baseline is HEAD\n3 files changed: 1 source, 2 test, 0 exempt\n\nCHECKS  (positive control: the work as written, clean worktree)\n  unit         pass  exit 0      0.0s\n\nNEGATIVE CONTROL  (1 source file(s) reverted to d28ac3e, tests kept as written)\n  unit         NOT DETECTED           exit 0\n       passed with 1 source file(s) reverted to d28ac3e\n\nVERDICT  fail (exit 3)\n  every check passed, and unit still passed with the work reverted. Green is not evidence here.\n```\n\nAn agent rarely forges a receipt. The receipt is usually real: the suite ran,\nit was green, the summary was accurate. What breaks is the assay underneath it.\nThe agent widens a mock until the unit under test never executes, asserts\n`toBeDefined()` instead of a value, writes a test against the stub it wrote\nfive minutes earlier, or pipes an exit code through `tee` so the status is\nswallowed. Then it runs the suite, gets a genuine green, and tells you it is\ndone.\n\nTools that check whether the suite *ran* cannot see any of that. Tools that\ndiff for tampering catch deletion, not substitution, and substitution looks\nlike work.\n\nThere is one question that separates a test from a formality: **if this change\nvanished, would anything have noticed?**\n\n```\npipx install mustfail          # or: pip install mustfail\n```\n\nOr run it from a clone with no install at all:\n\n```\ngit clone https://github.com/greenlitbooks/mustfail\nPYTHONPATH=mustfail/src python3 -m mustfail --help\nmustfail init        # writes a starter mustfail.json\nmustfail doctor      # is the gate wired, and would it fire?\nmustfail run         # positive control, negative control, verdict\nmustfail scan        # static scan only, milliseconds, no execution\nmustfail selftest    # run the corpus and check it against its manifest\n```\n\nMinimal config:\n\n```\n{\n  \"version\": 1,\n  \"source\": [\"src/**\"],\n  \"checks\": [\n    { \"id\": \"unit\", \"run\": \"npm test\", \"must_exit\": 0, \"control\": \"revert-source\" }\n  ]\n}\n```\n\nExit codes: `0` fine, `1` internal error, `2` a declared check failed, `3`\nchecks passed but a control was not detected, `4` a blocking swallow finding,\n`5` invalid config.\n\n1. **Isolate.** A throwaway`git worktree` is created at the baseline commit and\noverwritten with your work. Your own working tree is never touched. A tool\nwhose job is reverting files has exactly one unacceptable failure mode, and\nthis removes it.\n2. **Positive control.** Every check runs against the work as written. All must\npass. Without this step, a worktree missing its dependencies would \"detect\"\neverything and the tool would be confidently useless.\n3. **Negative control.** Every changed source file is reverted to the baseline.\nEvery test file stays exactly as the agent wrote it. The checks run again,\nand each one is expected to fail.\n4. **Classify.** A failure caused only by a missing import is reported as a weak\nsignal and not counted, because a new test importing a new function always\nbreaks on revert. That proves the symbol is referenced, not that anything is\nasserted.\n5. **Localize.** Only when a control was not detected, and only then, each source\nfile is reverted alone to name the file nothing covers.\n6. **Scan.** Independently and in milliseconds, the check commands and your CI\nconfig are scanned for constructs that discard a failing exit code.\n\nA check is *detecting* when its control failed in a way that counts. By default\none detecting check is enough, because the question is whether anything would\nhave noticed, not whether everything would have. A type checker or a corpus\nrunner will not be falsified by every source change, and failing the gate for\nthat is how gates get uninstalled.\n\nSet `require.control_detects` to `\"all\"` when every check really is meant to\ncover every source file. Set `on_weak_signal` to `\"fail\"` on a check where a\nload-time break should not count.\n\nChecks that did not detect are still reported, with the specific files named, even when the run passes. That reporting is where most of the value is: it is how you find out which part of a change nothing tests.\n\nThis costs roughly twice your suite. If your tests take ten minutes, do not put\nit on `Stop`. Put it on pre-push and in CI.\n\n```\nmustfail hook    # prints the block to paste into .claude/settings.json\n```\n\nThe `Stop` hook blocks the turn with a specific reason when a control was not\ndetected, then stands down after repeating itself so it can never trap a\nsession. See [docs/HOOKS.md](https://github.com/greenlitbooks/mustfail/blob/main/docs/HOOKS.md) for the full contract and the\nfailure modes that silently disable a gate.\n\nNothing about the engine is Claude-specific. `mustfail` never reads an agent\ntranscript. Everything it knows comes from git, the filesystem, and exit codes,\nwhich is why the same gate works under Codex, Cursor, a pre-push hook, plain CI,\nor a human being.\n\n`corpus/` holds hand-built repositories, each with one documented mechanism for\nmanufacturing a green run, each with the verdict it must produce recorded in\n`MANIFEST.json`. `mustfail selftest` builds every one in a throwaway git repo\nand checks the result.\n\n```\npass 001-test-written-against-the-stub\npass 002-assertion-free-test\npass 003-mock-swallows-the-unit\npass 004-pipe-swallows-exit-code\npass 005-honest-fix-and-test\npass 006-docs-only-change\npass 007-continue-on-error\n\n7/7 fixtures behaved as the manifest says.\n```\n\n`005-honest-fix-and-test` is the one that matters most. It is a real fix with a\nreal test, and it must stay green. A gate that fails good work gets uninstalled\nin a week, so the false-positive rate is the quality metric here, not the\ndetection rate. There are no detection percentages in this README and there\nwill not be any until real runs have been measured and the raw data published\nnext to the number.\n\nPlenty. [docs/LIMITS.md](https://github.com/greenlitbooks/mustfail/blob/main/docs/LIMITS.md) is not a formality: reverting whole\nfiles is crude, a pure refactor will look undetected, the failure\nclassification is string matching, the static scan is regex rather than a\nparser, and an agent that can edit files can edit the config. Read it before\nyou rely on this.\n\nThis is a small addition to a crowded shelf. Mutation testing has done the hard\nversion of this idea for decades, and at least a dozen agent-gating tools solve\nthe neighbouring problem. [docs/PRIOR-ART.md](https://github.com/greenlitbooks/mustfail/blob/main/docs/PRIOR-ART.md) names and links\nall of them, including the ones you should probably use instead.\n\nIssues and pull requests welcome. `mustfail` gates its own repository, so a\nchange that nothing tests will be blocked by the tool itself.\n\nMIT. Built and maintained by [Greenlit Books](https://greenlitbooks.com), whose\nfield guide on how AI fakes \"Done\" is free and needs no email address.", "url": "https://wpnews.pro/news/agent-s-tests-passed-prove-they-could-have-failed", "canonical_source": "https://github.com/greenlitbooks/mustfail", "published_at": "2026-10-29 07:33:36+00:00", "updated_at": "2026-10-29 07:50:01.069309+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-safety"], "entities": ["mustfail", "mustfail 0.1.0", "greenlitbooks", "Python 3.8", "git"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/agent-s-tests-passed-prove-they-could-have-failed", "markdown": "https://wpnews.pro/news/agent-s-tests-passed-prove-they-could-have-failed.md", "text": "https://wpnews.pro/news/agent-s-tests-passed-prove-they-could-have-failed.txt", "jsonld": "https://wpnews.pro/news/agent-s-tests-passed-prove-they-could-have-failed.jsonld"}}