Passing tests only prove the cases the tests actually try. When Codex, OpenAI’s coding assistant, writes a fix and the test for that fix in the same run, the test often checks what Codex believed the code should do, not what you asked for. Before you trust the green result, check three things: the new test can fail, the old tests were not weakened, and the test tries the exact case you care about.
Say you asked Codex to stop counting orders from internal test accounts in your monthly sales total, the number your manager reads every Monday. A few minutes later it reports that the fix is done and that a new test passes. You are about to merge it, which means adding it to the shared copy of the code. Then you open the new test and look at its sample data. It holds two ordinary orders and not a single test account. That test would have passed before the fix too, so it tells you nothing about whether the fix works.
This post is part of the ChatGPT Codex / coding tutorial. The previous post covered skills, tasks, and scheduled help. This one is about the tests that come back with that work: how a passing test can prove nothing, three quick proofs that it means something, and what to ask Codex for so its tests are worth trusting. If you still need to sort out when to use Chat, Work, or Codex, use the ChatGPT product map. The basics stay in the beginner series on learning ChatGPT.
Codex’s screens and approval steps keep changing, so treat the git commands below as patterns you can carry anywhere. The habits work the same with any coding assistant, and with code a person wrote.
Why can a passing test prove nothing?
A passing test proves only that the code gave the expected answer for the inputs the test tried. A test (a small program that runs your code with sample inputs and checks the answer) is only as good as those inputs and that expected answer. When the same assistant writes the code and the test, both come from one idea of the task. If that idea is wrong, the code and the test agree with each other and both miss what you meant.
There are five common ways a green result hides a problem:
- The sample skips the case. The test data never includes the thing the fix is about, like the test accounts in the scene above.
- The test copies the code. The expected answer was worked out by running the new code, so the test confirms the code matches itself.
- An old test was edited to pass. An expected value changed from 20.00 to 19.99, and the rounding bug became the new rule.
- A test was switched off. A skip marker (a label that tells the test runner to ignore that test) appeared, or a whole test file disappeared.
- The real work was faked. A mock (a stand-in that pretends to be the database or an outside service) returns a canned answer, so the test never touches the part that was broken.
None of these need bad intent. An assistant that is asked to make the checks pass will find the shortest path to green, and editing the check is sometimes shorter than fixing the code. Analysts know the same trap from AI-written SQL (the language for asking a database questions), where a query can run cleanly and still answer the wrong question. For that version of the habit, see How to check AI-written SQL before you ship it.
See it happen: a test that passes before and after the fix
Here is the sales total example as a small Python program you can run. It holds the old version of the function, the fixed version, the test Codex wrote, and a stronger test that includes one test-account order. It then runs both tests against both versions.
# The old version: counts every order in the month
def monthly_total_old(orders, month):
return sum(o["amount"] for o in orders if o["month"] == month)
# The fixed version: skips orders placed by internal test accounts
def monthly_total_new(orders, month):
return sum(o["amount"] for o in orders
if o["month"] == month and not o["test_account"])
# The test the agent wrote: no test accounts in the sample
weak_sample = [
{"month": "2026-09", "amount": 100, "test_account": False},
{"month": "2026-09", "amount": 50, "test_account": False},
]
# A test that includes the case the fix is about
strong_sample = weak_sample + [
{"month": "2026-09", "amount": 999, "test_account": True},
]
for name, fn in [("old code", monthly_total_old), ("new code", monthly_total_new)]:
weak = fn(weak_sample, "2026-09") == 150
strong = fn(strong_sample, "2026-09") == 150
print(f"{name}: agent's test passes={weak}, stronger test passes={strong}")Running it prints:
old code: agent's test passes=True, stronger test passes=False
new code: agent's test passes=True, stronger test passes=TrueLook at the first result on each line. Codex’s test passes on the old code and on the new code, so it cannot tell them apart. The stronger test fails on the old code and passes on the new code. That flip is the whole point of a test for a bug fix. A test that never fails on the broken version is decoration, however green it looks.
Proof one: make the new test fail on purpose
The fastest proof is to put the fix aside, keep the new test, and run it. If the test still passes without the fix, it does not test the fix. Git, the tool that tracks every change to the code, can set one file aside for a moment with git stash and bring it back afterward.
# Set the fixed code aside, keep the new test file as it is
git stash push -- src/reports/totals.py
# Run only the new test. It should FAIL now.
pytest tests/test_totals.py -q
# Bring the fix back and run it again. It should pass.
git stash pop
pytest tests/test_totals.py -qSwap in your own file names and your project’s test command (for example npm test in a JavaScript project). The order matters: fail without the fix, then pass with it. If the first run passes, go straight to proof three and ask for a test that includes the missing case. You can also ask Codex to do this check itself and paste you the failing output, but run it yourself the first few times so you know what a real failure looks like.
This check takes about a minute and catches the most common problem, a test whose sample never reaches the bug. Programmers call the idea “see it fail first,” and it is worth doing on every bug fix, whoever wrote the code.
Proof two: check whether the old tests were weakened
New tests are only half the story. The old tests were your safety net before Codex started, so any change to them deserves a careful read. Look at the changes to test files on their own, apart from the rest of the work, because they are easy to miss inside a long list of edits.
# Which test files changed, and by how much?
git diff --stat -- tests/
# Show removed or added lines that check answers or skip tests
git diff -- tests/ | grep -nE '^[-+].*(assert|expect|skip|xfail|only)'A line that starts with a minus was removed and a line with a plus was added. Here is the kind of pair that should stop you, an old expected value swapped for whatever the new code returns:
- assert round_price(19.995) == 20.00
+ assert round_price(19.995) == 19.99Sometimes the old value really was wrong and the change is correct. The point is that it is a decision about how your product should behave, so a person should make it on purpose and say why in the change message. These are the edits to look for:
| Edit to an old test | Why it matters | What to do |
|---|---|---|
| Expected value changed | The old behavior may have been right, and now the bug is the rule | Ask which one matches the product, and keep the old value unless someone decides otherwise |
| New skip or xfail marker | The test still exists but no longer runs or no longer counts | Remove the marker, or open a ticket that names the reason |
| Test file deleted | Every case it covered is now unchecked | Restore it and fix the code instead |
| Exact check loosened | An “equals” became “contains” or “roughly equal,” so wrong answers pass | Ask why the exact answer changed |
| New mock around the broken part | The test no longer reaches the real code that failed | Keep at least one test that runs the real code |
Proof three: compare the test with your request
The last proof is to read the test as plain English and hold it next to what you asked for. You do not need to understand every line of the code to do this. Look at three things: the test’s name, the sample data, and the expected answer.
- Write your request as one case in a sentence, such as “a month with a test-account order should leave that order out.” Writing it down keeps you from accepting a test that sounds close.
- Find that case in the sample data. If there is no test-account order, the test cannot check your request.
- Work out the expected answer yourself, by hand or in a spreadsheet, before you look at the test’s answer. If you copy the number from the test, you are trusting Codex’s sum again.
- Look for the edges: an empty month, a month with only test accounts, an order on the last day. Each one is a place where a fix can quietly break.
If a case is missing, ask for it by name. Asking for “more tests” usually gets you more of the same kind, so name the inputs and the answer you expect.
What to tell Codex so its tests mean something
The cheapest fix is in the request itself. Codex follows clear limits well, so put the test rules in the task instead of hoping for good habits. A request like this one closes most of the gaps above:
Fix monthly_total in src/reports/totals.py so orders from test
accounts are left out.
Tests:
- Add a test whose sample includes at least one test-account order.
- Show me that the new test fails on the current code before you fix it.
- Do not change, skip, or delete any existing test. If you think an
existing test is wrong, stop and tell me why instead.
- Do not mock monthly_total or the order data it reads.For rules you want on every task, put them in the project’s standing instructions file, which many projects call AGENTS.md. Two lines are enough to start: “Never edit, skip, or delete an existing test without saying so in your summary” and “For a bug fix, show the new test failing before the fix.” Then check the summary for that sentence, and treat a summary that does not mention test changes as a reason to look at proof two.
Codex and the ChatGPT desktop coding tools have approval steps, sandboxes (walled-off areas where Codex can work safely), and features tied to your plan, and these change over time. A sandbox keeps a bad run from touching the rest of your computer, but it says nothing about whether the tests inside it are any good. That part stays your job.
When is a green result enough?
A green result is enough when the tests were already there before Codex started, Codex did not touch them, and they already cover the area it changed. In that case the green result is real evidence, because the checks came from someone else’s idea of the task. It is still not a full review of the change, but it is a fair second opinion.
Be stricter when any of these is true, because each one means the tests and the code may share the same mistake:
- If the only tests for the change are ones Codex wrote in the same run, run proof one and proof three.
- If any existing test file changed, run proof two before anything else.
- The change touches money, sign-in, personal data, or a number someone reports upward. Ask a second person to read the tests as well as the code.
- The project had no tests for this area before. Green only means the new tests agree with the new code, so the hand-worked answer from proof three carries the weight.
Rule of thumb: Trust a test you have seen fail. A test you have only ever seen pass has not proved anything yet.
A 15-minute practice drill
Do this once on a small practice project, so the steps feel normal before a real deadline.
- Copy the sales total example above into a file, keep only the old version of the function, and add Codex’s weak test as a real test.
- Ask Codex to “fix monthly_total so test accounts are excluded, and add a test,” with no other rules.
- Run proof one with
git stash, and write down whether its new test failed without the fix. - Run proof two on the test folder, and note any line where an old test changed.
- Ask again with the test rules from the request above, and compare the two sets of tests side by side.
The difference between the two runs is the lesson. You will see how much the test rules in the request change what comes back.
Where this sits in the ChatGPT path
- Learn ChatGPT from scratch
- ChatGPT product map
- ChatGPT everyday tutorial
- ChatGPT Work tutorial
- ChatGPT Codex / coding tutorial (this series moves from opening Codex, to exploring, to skills, tasks, and scheduling, to tests, and then to git habits).
- For reading the whole change line by line and undoing it safely, see the review post in the Claude Code tutorial. Its steps work the same with Codex.
Next in this series come git-friendly habits for beginners, covering branches, small commits, and pull requests. After Codex, the ChatGPT track moves on to Custom GPTs (your own saved versions of ChatGPT, set up with standing instructions), which handle repeating playbooks that have nothing to do with code.
Quick recap
- A passing test proves only the inputs it tried, and a test written with the code can share the code’s mistake.
- Proof one: set the fix aside and watch the new test fail.
- Proof two: read every change to old tests, including changed answers, skip markers, deleted files, looser checks, and new mocks.
- Proof three: find your exact case in the sample data and work out the answer yourself.
- Put the test rules in the request and in your standing instructions file, so the tests come back worth trusting.
Your next step
Pick the last bug fix an AI assistant made in one of your projects and run proof one on it: set the fix aside with git stash and run its test. If the test still passes, you have found a real gap, and the request template above is the way to close it on the next task.
Sources
Research and further reading used for this article:
- OpenAI: Codex product page (coding agent product context)
- OpenAI: Introducing the Codex app (agent workflows and review surfaces evolve; re-check the current screens)
- Git: git-stash (setting changes aside and bringing them back)
- Git: git-diff (inspecting changes to a folder or file)
- pytest: How to use skip and xfail (what the skip markers do)
- AMS: How to check AI-written SQL before you ship it (the same “runs but answers the wrong question” habit in SQL)
- AMS: ChatGPT Codex / coding tutorial (this series)
- AMS: Learn (full series index)
Keep going
Same lessons in your feed
Short diagrams, hooks, and weekly tutorials on Substack, Instagram, X, and Facebook.
