You review a Codex PR that adds a User interface inside src/auth/types.ts. Three problems: 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 stops catching mismatches because they are structurally compatible enough to assign across.
Codex did not search before defining. It saw the local context, recognized the shape it needed, and inlined a fresh declaration. Without a forcing function — an explicit “search first” rule, a shared types module the agent knows to look at, or a CI check that flags duplicate definitions — this happens on every refactor task.
Common causes
1. Agent did not grep before creating
Codex’s default workflow is “read the file I am editing, add what is missing.” If the missing piece is a type, it writes one inline. It rarely does a repo-wide search for an existing definition.
How to spot it: PR introduces a new interface X or type X = .... grep -rn "interface X\|type X " src/ shows another definition elsewhere.
2. Types are scattered across the repo 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. Codex shrugs and adds to the local file.
How to spot it: find src -name 'types.ts' -o -name 'types/*.ts' returns ten or more paths. No src/types/index.ts re-exporting them.
3. AGENTS.md never tells the agent to search
The agent guidance has no rule about reusing types. Codex picks the locally-convenient path.
How to spot it: grep -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 that re-exports everything, but the agent only opened the file it was editing. It never saw the barrel and assumed nothing existed.
How to spot it: Transcript shows read_file calls only on the immediate file. No grep / search across the repo for the type name.
5. Type names overlap with common library names
Codex saw Response and assumed it was the DOM Response. It created a fresh ApiResponse rather than checking if you already had one. Common types (User, Item, Response, Error, Config) collide most often.
How to spot it: Diff adds a generic-named type. Existing one has the same generic name.
Shortest path to fix
Step 1: Add a “search before creating” rule to AGENTS.md
## Type and interface reuse
Before defining a new `type`, `interface`, `class`, or `enum`:
1. Run `grep -rn "interface <Name>\b\|type <Name>\b\|class <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 — do not fork.
4. New 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/`.
A rule the agent can actually follow: search command + decision tree.
Step 2: Centralize types in 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 in AGENTS.md:
All shared types are exported from `@/types`. Import from there:
import type { User } from '@/types'
If a type is not in `@/types`, it is module-private. Search `@/types` before
defining anything that could plausibly be reused.
A single import path makes “did this already exist” answerable in one grep.
Step 3: Add a ts-morph CI check for duplicate type names
// scripts/check-duplicate-types.ts
import { Project } from 'ts-morph'
const project = new Project({ tsConfigFilePath: 'tsconfig.json' })
const decls = new Map<string, string[]>()
for (const file of project.getSourceFiles('src/**/*.ts')) {
for (const iface of file.getInterfaces()) {
const name = iface.getName()
const list = decls.get(name) ?? []
list.push(file.getFilePath())
decls.set(name, list)
}
for (const alias of file.getTypeAliases()) {
const name = alias.getName()
const list = decls.get(name) ?? []
list.push(file.getFilePath())
decls.set(name, list)
}
}
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 to CI as a required check. Codex respects red CI.
Step 4: Suggest the existing type in the error message
If your codebase uses ESLint with custom rules, add a no-redeclare-shared-type rule that points at the canonical location. Even without custom rules, you can use eslint-plugin-import with no-duplicates and no-restricted-imports to keep 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 self-corrects.
Step 5: Review imports in agent PRs first
In your PR checklist:
- [ ] No new `interface` or `type` declarations duplicating something in `@/types`
- [ ] All new types go in `src/types/<domain>.ts`, not inline in feature files
- [ ] `npm run check:types` passes (runs the ts-morph duplicate check)
This is the human safety net for cases ts-morph cannot catch (slightly different shapes with the same name).
Prevention
- AGENTS.md mandates “grep before define” with the exact grep command
- All shared types live in
src/types/and re-export fromsrc/types/index.ts - ts-morph CI check fails on duplicate interface/type 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 longer prefix to avoid future collisions
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