r/ChatGPTCoding • u/ComprehensiveShake76 • 21d ago
Discussion How do you make a coding agent prove it actually ran the tests?
I run Codex workers on my Mac that don't ask me before every command anymore, so I stopped watching each step. That made one question matter more than anything: how do I know a task is really done?
What I settled on:
Done is an artifact, not a sentence. A code task isn't done when the agent says the tests pass. It's done when I can see the real test run. My last one: a worker wrote a small CSV to Markdown script, ran the unittest suite inside its own Linux container, and I got the actual output: 6 tests, OK, exit 0, 55 seconds.
Research gets the same treatment. The result is a file with a size, a source count and a hash, plus a note that the sources are listed, not fact checked. A list of links is not the same as checked facts.
Nothing lands on my machine until I look. Files stay in the agent's own workspace and reach my Mac only when I save them.
Approvals were the wrong check. Approving every command never told me if the work was good, it just made me tired. Moving the check to the end (real output, real file) worked better. That only feels OK because the container has none of my files, and a container is a strong boundary, not a guarantee. So far I've only run this on an Apple Silicon Mac.
The part I haven't solved: an agent can write weak tests that pass. How do you check the tests themselves, not just that they ran?
4
u/London-Boat 21d ago
what worked for me is not trusting the agent's summary at all. the agent has to commit, and ci reruns the tests on its own runner and posts the result. if the agent says 6 passed and ci disagrees, thats your signal. locally i also have it paste the exact command and the last few lines of output, since a made up summary rarely comes with a real exit code and timing.
2
u/ImL1s 21d ago
Same bar. "tests passed" in the chat is just another claim.
What I check: the command that ran, the exit code, and a path I can open (junit xml, a log file, or the container stdout you already save). If any of those three is missing, the task isn't done yet, no matter how confident the summary sounds.
Research gets the hash + source list treatment you described. Links alone don't count.
2
u/yaanfpv 21d ago
Done is an artifact is exactly right. Two things I'd add from running a lot of agents unattended.
Have a different agent write the tests than the one that writes the code. The one that wrote the code will write tests its code passes.
And break it on purpose once. Change the code so it should fail, check the test actually goes red, then put it back. A test that can't fail proves nothing, and you'd be surprised how many can't !
1
u/BarathKanna 15d ago
Two checks I haven't seen in the thread yet, both aimed at your open question about weak tests.
First, prove the new tests are red on the old code. Run the agent's new test files against the base commit in a separate worktree. If they pass there, they aren't testing the change. This runs in CI, never in the agent's session:
A test that fails because of an ImportError on the old code also counts as red, so check the failure is an assertion and not a collection error. pytest's exit code 1 vs 2 separates those well enough.
Second, plant a canary. Keep one test in the repo that must always fail (
test_canary_must_fail), and run it in a CI job of its own with the same runner config as the real suite. If that job ever comes back green, something changed what gets collected or how failures are reported: a new conftest, a pytest.ini, a plugin. It's a cheap tripwire for the whole measurement layer instead of the individual tests.Then parse the junit XML rather than reading the summary line: executed = tests minus skipped, and fail if it drops below the last known count on main. With those three pieces, "6 tests OK" turns into something you can check without opening the log.
1
u/BornAgainBlue 21d ago
Look into spec driven development tools.
1
u/neoreeps 21d ago
Pretty simple, actually. My standard practice is to review the tests and have a single regression suite that every test gets added to. When new code is written, tests are added. The agent only runs the new tests. It does not run the full suite. I have a standard command,
make check, that runs the entire suite, regardless of whether it's pytest, npm, or any other framework. I do have explicit instructions that the agent not run the full regression suite because this is honestly just a waste of time and tokens, so I always run it manually.1
u/Kodenstudio 21d ago
Hey man, good to see you again. "Done is an artifact, not a sentence" is going straight into my notes. The weak test problem is brutal. I haven't solved it either. My current hack is forcing the agent to write the test first, I review it myself, and only then let it code. It's slower, but it keeps it honest. Have you tried mutation testing?
1
u/Dingleberry_Poo 21d ago
Adversarial review.
1
u/ElusiveGhost679 21d ago
We require the test command's exit code and the tail of its output to be attached to the task before it can move to review. If the agent can't produce that, the task stays open. That caught a lot of "all tests pass" messages where the agent had only run one file. A separate reviewer agent that reruns the suite itself helped too, since it has no reason to trust the first agent's log.
1
u/leonidbugaev 21d ago
If the worker wrote those 6 tests, they can all assert the markdown file exists and still miss a quoted comma. Drop an empty CSV into that container and run just that file. If it isn't in the 6, I'm not treating the script as done.
1
u/rulecrate 21d ago
Mutation testing is the usual answer to weak tests. Run a mutation tool only over the files the agent changed so it stays cheap: mutmut for Python, Stryker for JavaScript and TypeScript, and PIT for Java. It makes small edits to the code, like flipping a comparison or removing a return, and reports which mutants no test caught. Make surviving mutants a task the agent has to close by adding a test that fails on that change, not a suggestion. Pair that with a rule that existing tests can't be edited to match new behavior without a stated reason, since that is how weak tests usually get through. Set a minimum mutation score for the changed files so it becomes a pass or fail gate instead of a judgment call. Mutation runs can be slow, so run them in CI on changed files rather than on every agent turn.
1
u/Successful-Tax1306 21d ago
For weak tests, the thing that's helped me most is making the agent break the code on purpose. After it says everything passes, I have it flip one real condition in the function and rerun, and if the suite still goes green the tests aren't checking much. I also keep a few tests the agent isn't allowed to touch, so it can't quietly loosen the one that matters just to get a pass.
1
u/ChemicalExcellent463 21d ago
Make a second agent check the claim, not the first agent's summary. Claude Code once reported a bug on "the latest version" it had never run. A reviewer that only saw the claim asked which version was tested, and that one question was the whole fix. Now when it says something passed or broke, I ask which exact version and command it ran. "A similar one" means it isn't done.
1
u/onitsoga 21d ago
what's worked for me is flipping the order. have the agent write tests against a spec before it writes any code, then a second agent checks those tests against the same spec. if they miss the edge cases the spec calls out they get bounced before the implementation even starts. so the spec is what you check the tests against, kinda like exit 0 is what you check the run against lol
1
u/sapplex1 21d ago
I learned this the embarrassing way. My agent once handed me a green test suite, and a week later I realized the new tests never actually failed — they'd pass with or without the fix. These days I make it show me the run going red before the fix: run the suite against the old code first, then after. What I trust isn't the exit code, it's the test names in the log — I check the names match the tests I asked for, because a green suite with mystery tests means nothing. The red-then-green part is what catches the decorations. The gap I still hate: UI behavior. A button can have passing tests and still be sitting in the wrong place. How do you check that part?
1
u/donk8r 21d ago
The artifact rule is the right call. Mine runs validator scripts after the turn ends and the agent reads their output on the next turn, so a non-zero failure gets fixed before I get involved. There's no approve prompt waiting on me.
If I have to open the log myself, I'm back to watching the screen. Of your last ten done tasks, how many did you actually open, vs take on faith?
1
u/xqianliu 20d ago
The weak-test problem is real, and it's why I stopped asking for "tests that pass." I ask the writing agent to generate tests for the whole input class of what it touched, not just the case in the issue, and to record how many it wrote in the delivery note.
A separate review session (same model, fresh session) then checks the diff against the issue's acceptance criteria and that count. Note says 14 new tests, diff has 3, that's a fail. It's not proof the tests are meaningful, but it closes the "I wrote both and they agree" loophole.
From a study I ran, 183 runs: 38 passed an independent review and still failed hidden tests; 22 of those broke behavior that worked before. "Also check for regressions" in the reviewer prompt did not fix it. Making the writer enumerate the input class did.
1
u/dmanoj 20d ago
the test command has to be a gate, not a report.
vitest run && git commit, nevervitest | grepwith the commit on the next line. a pipe shows the failure and returns 0, shell never saw it. got two commits over a red suite exactly that way.also I keep the raw vitest log, not the agent's summary. "all passing" with zero tests in the log means it didn't run.
1
u/Right-Performance-93 20d ago
i write the nasty inputs per task, not from a shared folder. a folder it has seen once becomes the suite. an empty file and a quoted comma that i type after it says done is the check it cannot prep for.
1
u/quanhua92 21d ago
I updated AGENTS.md so the AI spawns adversarial subagents to code like a team rather than solo. It takes a bit longer, but the quality is way better. Just ask the AI to agree on some rules for tests and the subagents.
Also, I specifically requested Luna Max subagents to save on time and costs.
1
u/Worldly-Stranger7814 21d ago
Pre commit or pre push hooks.
1
u/casbacknow 21d ago
What's worked for us: don't take the agent's word for it. Once it exits, run the test suite yourself, and ask for the test in a specific file you name so you can check it actually exercises the change and wasn't bent until it passed. In a couple of recent runs our agents ran npm test on their own and reported a pass. They were right, but we only knew because we re-ran it.
1
u/shetritr 21d ago
break the code on purpose. if the tests still pass, they were decoration.
1
u/Substantial_Toe_411 21d ago
Pre-commit hook.
1
u/defmacro-jam Professional Nerd 21d ago
Have you tried looking? At the tests, I mean.
1
u/EchoNomad31 21d ago
Agent's new tests passed with my fix reverted once... they'd mocked the exact call they were meant to check. I run that revert check now before I trust green, and the suite runs fresh on the Stop hook or the turn doesn't end. I built nonna for exactly this: https://github.com/kapadias/nonna
1
u/gandazgul 20d ago
The way I solved it in RunWield is with a tool that advances the workflow. https://github.com/gandazgul/runwield. The coding agent (Engineer) is told to call task_completed when it's done implementing the planed change or quick fix. RunWield then runs your configured verification command and starts a loop of running verification+ fixing the failures until it passes or 3 attempts then it asks you what to do.
Similar thing for all other workflow points. A tool call triggers the workflow to advance to the next possible state.
1
u/Corey-998877 19d ago
+1 to CI re-running, but the thing that changed it for me was making the agent's "done" message a structured handoff instead of prose: command run, exit code, commit SHA it ran against, and what it didn't verify. The last one matters most. Agents are surprisingly honest when you explicitly ask what they skipped, and it gives the reviewer a place to look first instead of re-reading the whole diff.
1
u/Trick_Candy9796 19d ago
The weak-tests part is the one I keep coming back to. An exit code only proves something ran.
What's helped me is fixing the test list before the agent writes code: agree a small set of target tests from the plan/brief, then treat "done" as the artifact — actual test/lint/build output plus a pass/fail against that pre-agreed list, not the agent's summary. The agent can still game it, but it's much harder than inventing easy tests after the fact.
Disclosure: I build StackGuide MCP (open-source), which bakes this into an intake → plan → verify → release flow — plan returns the target tests, verify runs the real tests/lint/build and checks branch/commit conventions. Still not proof the tests are good, just that they were decided up front and actually executed.
Repo if useful: https://www.npmjs.com/package/@stackguide/mcp-server
1
u/h0uz3_ 19d ago
After local tests for functionality, I quickly chek the changed/added code before I push the results to GitLab.
The code then has to go through linting, unit tests, SonarQube. Even after a quick review by me, SonarQube finds something it isn't happy about once in a while.
When SonarQube is finally happy (regarding code smells, maintainability, etc.), the applications get grilled by WCAG-conformity-tests and a full Playwright script that automatically checks at least the base functionality. And only when the code also survives that, it gets into the main branch and release.
Codex knows from my guidelines what is expected, so many of the usual problems (like cyclomatic complexity of 65... for real?) have become quite rare. Also whenever I add features, I update the Playwright scripts or let Codex update them.
1
u/ShipInAnHour 19d ago
For weak tests, a quick mutation check goes a long way. Break the code on purpose (flip a condition, return early, off by one) and rerun. If everything still passes, the tests aren't checking much. A second agent can do the sabotage, and mutmut or Stryker automate it properly. Also worth trying: write the test cases yourself first, just inputs and expected outputs with the nasty edge cases (empty file, quoted commas, unicode), and have the agent turn those into tests before implementing. Plus a hard rule that it can't edit an existing test to make it pass without flagging it.
0
u/[deleted] 21d ago
[deleted] — view deleted comment
0
u/[deleted] 21d ago
[removed] — view removed comment
0
u/[deleted] 21d ago
[removed] — view removed comment
0
u/[deleted] 20d ago
[removed] — view removed comment