Codex Makes Unsafe Assumptions

Codex confidently uses a column that doesn't exist, an env var with the wrong shape, or a function signature it never read. Force quote-before-write grounding.

Codex wrote user.handle in a SELECT — but the column is user.username. Or it called sendEmail(to, subject, body) when the signature is sendEmail({ to, subject, html }). Or it hard-coded https://api.example.com when your project reads it from process.env.API_URL. The code looks right. It doesn’t compile, doesn’t run, or worse, runs against the wrong target.

This is “confident hallucination”: Codex inferred a shape from prior patterns instead of reading your actual file. Without grounding, even capable models guess. Fix it by making “quote before write” a hard rule, anchoring every generated line to a real file Codex must read first.

Common causes

Ordered by hit rate, highest first.

1. Codex didn’t open the file — inferred from imports

The prompt named a file, but Codex never actually opened it. It guessed the function’s shape from how it’s imported elsewhere or from its training-time prior. Arguments are subtly wrong.

How to spot it: Search the chat for a tool call reading the relevant file. If absent, Codex worked from imagination.

2. Stale AGENTS.md / CLAUDE.md documenting a past reality

Your docs say users table has handle. Six months ago you renamed it to username. Docs not updated; Codex trusted them.

How to spot it: Grep AGENTS.md for the wrong symbol Codex used. If present, the doc is stale.

sendEmail(to, subject, body) is the canonical Node.js shape from countless blog posts. Your project uses an options-object variant. Codex defaulted to the popular shape.

How to spot it: The wrong signature matches an obvious open-source library’s shape. Codex picked the meme, not your code.

4. Schema lives in a generated file Codex didn’t read

Prisma’s schema.prisma is the source of truth, but the generated node_modules/.prisma/client/index.d.ts is where types live. Codex read neither, fell back to guessing.

How to spot it: Wrong types/columns where Codex should have used Prisma’s generated types. Check if Codex’s reads include schema.prisma or the generated .d.ts.

5. Env var format invented

You document STRIPE_KEY=sk_test_.... Codex used STRIPE_API_KEY (extra word). Or it wrote process.env.PORT as a number directly — but env vars are strings, and your code needs parseInt.

How to spot it: Compare Codex’s env var names + shapes against .env.example. Mismatches are usually invented.

6. Codex over-trusted comment / JSDoc that’s out of date

The function has a JSDoc saying @param userId: string but the implementation actually accepts number | string now. Codex trusted the doc, called it with string, runtime fine — until the caller passes a number elsewhere and the type system doesn’t catch the mismatch.

How to spot it: Implementation signature differs from JSDoc. JSDoc is the lie; implementation is truth.

Shortest path to fix

Ordered by ROI. Step 1 alone catches 80% of unsafe assumptions.

Step 1: Quote-before-write rule

In every code-generating prompt:

Before writing any code that calls an external function, schema, or env var:

1. Read the file where it's defined.
2. Quote the relevant lines verbatim in your reply (signature, columns, exports).
3. ONLY THEN write the code that uses it.

If you cannot quote it (file not found, doesn't compile), STOP and ask.
Do not infer from imports or training-time priors.

This adds one extra step but eliminates the “I assumed it looked like X” failure mode.

Step 2: For schemas, demand the schema read first

You're writing a query against `users`.
First: read `prisma/schema.prisma` and quote the `User` model verbatim.
Then: write the query, using ONLY columns that appear in the quoted model.
If a needed column doesn't exist, propose adding it (do not assume).

For DB-less environments, point at the migration file or DDL.

Step 3: For function signatures, demand the file read first

You're calling `sendEmail`.
First: read `src/lib/email.ts` and quote the export signature.
Then: write the call, matching that exact signature.

If Codex can’t quote it, the file may not exist — surface that mismatch before code lands.

Step 4: For env vars, anchor to .env.example

You're using an env var.
1. Read `.env.example` and quote the relevant lines.
2. Use the exact name from `.env.example` — do not invent variants.
3. If the var isn't in `.env.example`, propose adding it.

Step 5: After Codex writes, verify with grep

Don’t trust the report — check that referenced symbols actually exist:

# For each unique identifier in the new code, grep for its definition
grep -rn "function sendEmail\|export const sendEmail\|export function sendEmail" src/
grep -rn "model User\|interface User" prisma/ src/

Empty results = symbol Codex used doesn’t exist.

Step 6: Refresh stale AGENTS.md before relying on it

If your AGENTS.md is older than 3 months and the codebase changes a lot, it’s lying to Codex. Audit:

# When did key docs last update vs key files?
git log -1 --format="%ai" AGENTS.md
git log -1 --format="%ai" prisma/schema.prisma
git log -1 --format="%ai" src/lib/email.ts

If schema/email updated after AGENTS.md, refresh AGENTS.md or remove the stale section so Codex reads source-of-truth instead.

Prevention

  • “Quote before write” is a permanent AGENTS.md rule, not a per-prompt instruction
  • For schemas, env vars, and function signatures, name the source-of-truth file in AGENTS.md
  • Treat generated types (Prisma, GraphQL codegen) as truth — point Codex at them
  • Audit AGENTS.md quarterly; remove sections that no longer match the code
  • After Codex writes, grep the referenced symbols — if any are missing, it hallucinated
  • Confidence in AI is uncorrelated with correctness; verify instead of trusting tone

Tags: #Codex #Coding agent #Troubleshooting #Debug #Unsafe assumptions