You review a Codex PR that adds a User interface inside src/auth/types.ts. Three problems at once: you already have User in src/types/user.ts, the new one has slightly different fields, and now half the codebase imports the old User and half the new one. TypeScript stays quiet because the two shapes are structurally compatible enough to assign across — so the bug surfaces at runtime, not at compile time.
Codex did not search before defining. It read the file it was editing, recognized the shape it needed, and inlined a fresh declaration. Without a forcing function — an explicit “search first” rule in AGENTS.md, a single import path the agent knows to check, and a CI check that fails on duplicate names — this repeats on every refactor task.
TL;DR — fastest fix
- Add a Type reuse rule to
AGENTS.mdthat tells Codex to run arg(ripgrep) search for the name before declaring it, and to import the existing one if found. Codex already usesrgas its search backbone, so this is a tool it runs instinctively (as of June 2026). - Make
src/types/index.tsa barrel and route all shared types through@/types, so “does this already exist?” is answerable in one search. - Add a ts-morph CI check that fails the build on duplicate
interface/type/enum/classnames. Codex respects red CI and self-corrects.
Step 1 alone stops most cases. Steps 2 and 3 close the gap for generic names like User and Response that collide most often.
Common causes — which bucket are you in
| Symptom in the PR / transcript | Likely cause | Fix |
|---|---|---|
New interface X added; rg "interface X" src/ shows another one | Agent never searched before creating | AGENTS.md “search first” rule (Step 1) |
find src -name 'types.ts' returns 10+ paths, no central index | Types scattered, no obvious home | Types barrel at @/types (Step 2) |
rg -i 'type|interface|reuse' AGENTS.md returns nothing | No guidance to reuse types | AGENTS.md rule (Step 1) |
Transcript only shows read_file on the edited file, no search | Existing type sits in an unloaded barrel | Barrel + AGENTS.md pointer (Step 1 + 2) |
Diff adds a generic-named type (User, Response, Config) | Name collides with a DOM/library type, agent assumed it was that | Broader search + ts-morph CI (Step 1 + 3) |
1. Agent did not search before creating
Codex’s default loop is “read the file I am editing, add what is missing.” If the missing piece is a type, it writes one inline. It does not always run a repo-wide search for an existing definition first.
How to spot it: the PR introduces a new interface X or type X = ..., and rg -n "interface X|type X " src/ shows another definition elsewhere.
2. Types are scattered with no central index
Half your types live in src/types/, the other half are colocated with their first user (src/auth/types.ts, src/api/types.ts). There is no obvious “where types live” answer, so Codex adds to the local file.
How to spot it: find src -name 'types.ts' -o -path '*/types/*.ts' returns ten or more paths, and there is no src/types/index.ts re-exporting them.
3. AGENTS.md never tells the agent to search
Codex loads AGENTS.md before it does any work (it walks from the Git root down to the working directory, closest file wins, with a combined cap of 32 KiB by default). If that file has no rule about reusing types, Codex takes the locally convenient path.
How to spot it: rg -i 'type|interface|reuse' AGENTS.md returns nothing.
4. Existing type lives in a barrel the agent did not load
You have src/types/index.ts re-exporting everything, but the agent only opened the file it was editing. It never saw the barrel and assumed nothing existed.
How to spot it: the transcript shows read_file calls only on the immediate file, with no rg / search across the repo for the type name.
5. Type name overlaps with a common library name
Codex saw Response and assumed it was the DOM Response, so it created a fresh ApiResponse instead of checking whether you already had one. Generic names (User, Item, Response, Error, Config) collide most often.
How to spot it: the diff adds a generic-named type, and an existing one has the same name.
Shortest path to fix
Step 1: Add a “search before creating” rule to AGENTS.md
Codex runs rg (ripgrep) constantly during its planning loop, so a rule phrased as an rg command is one it will actually execute:
## Type and interface reuse
Before defining a new `type`, `interface`, `class`, or `enum`:
1. Search for the name first:
`rg -n "(interface|class|enum) <Name>\b|type <Name>\b" src/`
2. If a definition exists, import it. Do not duplicate.
3. If the existing one is missing fields you need, extend it
(`interface X extends BaseX`) or update it in place — do not fork.
4. New shared types belong in `src/types/<domain>.ts`. Do not inline new
types in feature files unless the type is private to one module.
When a name is generic (`User`, `Item`, `Response`), search broadly —
include `packages/`, `apps/`, and `shared/`.
This is a rule the agent can actually follow: one search command plus a decision tree. Keep AGENTS.md under the 32 KiB cap so none of it gets truncated.
Step 2: Centralize types behind a single import path
Make src/types/index.ts a barrel that re-exports every shared type:
// src/types/index.ts
export type { User } from './user'
export type { Session } from './session'
export type { ApiResponse, ApiError } from './api'
export type { Permission } from './permission'
Then point AGENTS.md at it:
All shared types are exported from `@/types`. Import from there:
import type { User } from '@/types'
If a type is not in `@/types`, treat it as module-private. Search `@/types`
before defining anything that could plausibly be reused.
A single import path makes “did this already exist?” answerable in one search.
Step 3: Add a ts-morph CI check for duplicate type names
This catches what review and AGENTS.md miss. The script below uses ts-morph (28.x as of June 2026) and covers interfaces, type aliases, enums, and classes — the same four kinds the AGENTS.md rule names:
// scripts/check-duplicate-types.ts
import { Project } from 'ts-morph'
const project = new Project({ tsConfigFilePath: 'tsconfig.json' })
const decls = new Map<string, string[]>()
const record = (name: string, path: string) => {
const list = decls.get(name) ?? []
list.push(path)
decls.set(name, list)
}
for (const file of project.getSourceFiles('src/**/*.ts')) {
const path = file.getFilePath()
for (const d of file.getInterfaces()) record(d.getName(), path)
for (const d of file.getTypeAliases()) record(d.getName(), path)
for (const d of file.getEnums()) record(d.getName(), path)
for (const d of file.getClasses()) {
const name = d.getName()
if (name) record(name, path)
}
}
const dupes = [...decls.entries()].filter(([, files]) => files.length > 1)
if (dupes.length > 0) {
for (const [name, files] of dupes) {
console.error(`Duplicate type ${name}:`)
for (const f of files) console.error(` ${f}`)
}
process.exit(1)
}
Wire it to a required CI check (npm run check:types). Codex reads failing CI output and self-corrects on the next turn.
Step 4: Make ESLint point at the canonical location
If your codebase has custom ESLint rules, add one that flags re-declaring a shared type and points at the canonical path. Even without custom rules, eslint-plugin-import with no-duplicates plus no-restricted-imports keeps imports flowing through @/types:
{
"rules": {
"no-restricted-imports": ["error", {
"patterns": [{
"group": ["**/auth/types", "**/api/types", "**/user/types"],
"message": "Import shared types from @/types, not deep paths."
}]
}]
}
}
Codex reads ESLint output and fixes the import path itself.
Step 5: Review imports in agent PRs first
This is the human safety net for cases ts-morph cannot catch — same name, slightly different shape. Add to your PR checklist:
- [ ] No new `interface` or `type` declarations duplicating something in `@/types`
- [ ] All new shared types go in `src/types/<domain>.ts`, not inline in feature files
- [ ] `npm run check:types` passes (runs the ts-morph duplicate check)
How to confirm it’s fixed
- Run the duplicate check locally:
npm run check:typesshould exit0and print nothing. - Confirm Codex loaded the new rule. From the repo root, run
codex --ask-for-approval never "Summarize the type-reuse rules in AGENTS.md."and check that the rule appears in its answer. If it does not, yourAGENTS.mdis past the 32 KiB cap or in the wrong directory. - Give Codex a task that touches a shared type and read the transcript: you should see an
rgsearch for the type name before any new declaration.
Prevention
AGENTS.mdmandates “search before define” with the exactrgcommand- All shared types live in
src/types/and re-export fromsrc/types/index.ts - ts-morph CI check fails on duplicate interface / type / enum / class names
- ESLint
no-restricted-importskeeps imports flowing through@/types - Reviewers spot-check new
interfaceandtypedeclarations in agent PRs - For generic names (
User,Item,Response) require a domain prefix to avoid future collisions
FAQ
Why doesn’t TypeScript just catch the duplicate? Because the two declarations live in different files with different module scopes, they are two distinct types that happen to share a name. As long as their fields are structurally compatible, TypeScript lets you assign one to the other. You only feel the bug when a field exists on one shape but not the other.
Should I use rg or grep in the AGENTS.md rule?
Use rg (ripgrep). Codex, Cursor, and most coding agents use ripgrep as their search backbone as of June 2026 — it respects .gitignore, is far faster on large repos, and is the command Codex reaches for by default. Plain grep still works if rg is not installed, but the agent is less likely to run it unprompted.
Codex keeps ignoring the rule. What now?
First confirm it loaded: run codex --ask-for-approval never "Summarize the type-reuse rules in AGENTS.md.". If the rule is missing from the answer, your AGENTS.md likely exceeds the default 32 KiB cap (project_doc_max_bytes) or sits outside the path Codex walks (Git root down to the working directory). Trim the file or move the rule into the closest AGENTS.md to the code being edited — the nearest file wins.
How do I clean up the duplicates already in the repo?
Run the ts-morph script in Step 3 to list every duplicate and its file paths. Pick the canonical declaration, delete the others, then let the type-checker drive the fix: every broken import points at code that needs to switch to @/types. Update them and re-run npm run check:types until it is green.
Does this apply to Codex in the IDE and the CLI?
Yes. Both read the same AGENTS.md chain (global ~/.codex/AGENTS.md, then project files from the Git root down). The ts-morph CI check and ESLint rules run in your pipeline regardless of which Codex surface produced the PR.
Related
- Codex duplicates files instead of editing
- Codex ignores project structure
- Codex misses project conventions
- Codex style doesn’t fit
- Codex uses deprecated API
- Codex patch conflicts existing code
Tags: #Codex #agent #Troubleshooting #types