Claude hallucinated identifiers: writes column/field/config names from memory instead of reading source files
Problem
Claude generates code using hallucinated identifiers -- column names, field names, method signatures, and API contracts fabricated from its training data or "what sounds right" rather than read from the actual source files in the project. This happens even when the correct source files are open, available, and explicitly referenced in project instructions.
This is not an occasional typo. Over a 30-session, ~90K LOC migration project with 93 documented bugs (KB-001 through KB-093), identifier hallucination was the single most frequent root cause category, appearing in 7+ distinct KB entries and causing cascading failures across services, routes, frontend, and infrastructure.
---
The Core Behavior
Claude writes code that references identifiers it has never verified exist in the target codebase:
- Column names -- writes
voucher_datewhen the actual column isdate; writesparty_typewhen the table hasis_debtor/is_creditorbooleans; writestotal_drwhen the table has per-type amount columns; writesmonthwhen the column isyear_month - Field names -- references
user.company_idson a User model that has no such field - API contracts -- backend expects
{refresh_token}in POST body but Claude writes frontend that sends empty body - Config keys -- sets
REDIS_URLin compose but readsCELERY_BROKER_URLin code
The pattern is consistent: Claude constructs a plausible-sounding name from context ("voucher_date sounds more descriptive than date", "party_type sounds right for a party classification") rather than reading the 20-line model file that contains the actual name.
---
Incident Timeline (all from one project, with KB tracking IDs)
Incident 1: KB-007 -- Systemic hallucination across 4 service files (2026-04-23)
What happened: Claude generated service layer code for auth, TDS config, and dashboard services. Every file referenced column/field names that do not exist on the actual ORM models.
Specific hallucinations:
auth_service.py: referenceduser.company_ids-- User model has no such field (access is via M:Nuser_companiestable)tds_config_service.py: usedid,pan,tds_section,branch_idon a model that has composite primary keys with different column namesdashboard_service.py: queried_summary_monthly.month-- actual column isyear_monthdeps.py: referenceduser.company_idsagain (same hallucination, different file)
Root cause from KB-007: "Claude generated service layer code from MEMORY of what the desktop had, without reading the actual web ORM models first."
Impact: Every service file had to be manually audited for column name correctness. None of these would have been caught by py_compile or basic linting -- they only crash at runtime when the query executes.
Incident 2: KB-013 -- Same hallucinated name in 3+ files, persists after fix (2026-04-23)
What happened: Claude used voucher_date (hallucinated) instead of date (actual column) across 3 service files: compliance_service.py, party_pnl_service.py, phase6_advanced.py.
Key detail: KB-005 had already been written earlier in the same project documenting that date was the correct column name. Claude had access to its own Known_Bugs.txt but did not check it before writing the same incorrect name again.
From KB-013: "KB-005 already documented that date is the column name and warned about shadowing -- I didn't check my own KB."
Pattern: Fixing a hallucinated name in one file does not prevent Claude from hallucinating the same wrong name in other files. Claude does not do project-wide grep after fixing a name error.
Incident 3: KB-015 -- Intelligence table column hallucination (2026-04-23)
What happened: Intelligence tables are created via raw SQL CREATE TABLE in phase builder files (no ORM model). Claude wrote service queries against these tables using hallucinated column names.
Specific hallucinations:
- Queried
_vi_voucher.voucher_date-- actual column isdate - Queried
_summary_monthly.month-- actual column isyear_month - These are the same hallucinations as KB-007 and KB-013, repeated in yet another context
From KB-015: "Column names in CREATE TABLE are not ORM models -- no autocomplete, no type checking. Service authors guess column names instead of reading the CREATE TABLE statement."
Impact: The intelligence phase builders define ~52 tables with ~300+ columns total. Every consumer service query is a potential hallucination site because there is no ORM to constrain the names.
Incident 4: KB-057 -- 23 invented column names across export/dashboard (2026-04-26)
What happened: A security audit found that export, dashboard, and Phase 6 analytical services all queried intelligence tables using column names that the builders never create.
Specific hallucinations (from KB-057):
party_type-- table hasis_debtor/is_creditor(boolean flags, not a type enum)total_dr/total_cr-- table has per-type amount columns with different namesledger_name-- table hasnamecredit_note_amount-- table hascn_amount- 19 more column name mismatches across 3 service files
From KB-057: "Export/dashboard services were written from memory or documentation drafts, not by reading the actual CREATE TABLE in phase2_summaries.py / phase3_reports.py."
Critical detail: This was found 3 days after KB-007/013/015 had already established the "never write column names from memory" rule. Despite the rule being in CLAUDE.md, Known_Bugs.txt, AND the pre-change-checklist, Claude continued hallucinating column names.
Incident 5: KB-008 -- Frontend/backend API contract hallucination (2026-04-23)
What happened: Backend and frontend were written in separate sessions. Claude hallucinated the API contract rather than cross-referencing.
Specific mismatches:
- Backend
POST /auth/logoutexpects{refresh_token}in request body -- frontend sends empty body - Backend
POST /auth/refreshexpects{refresh_token}in body -- frontend sends nothing (was supposed to use httpOnly cookie) - Both endpoints return 422 Unprocessable Entity at runtime
From KB-008: "Backend and frontend were written in separate sessions without cross-referencing."
Incident 6: KB-010 + KB-014 -- Infrastructure config hallucination (2026-04-23)
What happened: Docker Compose and Celery config used different environment variable names for the same connection.
Specific mismatches:
docker-compose.ymlsetsREDIS_URL--celery_app.pyreadsCELERY_BROKER_URL- Celery worker finds zero registered tasks because
celery -A app.taskswas configured but Celery app is defined inapp.core.celery_app - ENV vars added to worker service but not backend service (both import the same celery_app module)
Pattern: Claude hallucinated config key names instead of reading the config module that consumes them.
Incident 7: KB-093 -- 217 gaps found in "completed" migration (2026-04-27)
What happened: After a claimed-complete 85-file fix sprint, a deep line-by-line audit (7 agents reading ~90K LOC ProOneIQ source as truth) found 217 gaps, 27 of which were CRITICAL.
Hallucination-related findings:
ABS(opening)destroys the sign convention -- source preserves sign- Entry Dr/Cr ignores the
ISDEEMEDPOSITIVEflag that Tally embeds in XML - Voucher FETCH XML missing
.LISTsuffix on sub-collections (Tally returns nothing without it) - Intelligence tables had different grain (aggregation level) than source -- redesigned by Claude without cross-reference
- TDS always uses individual tax rate, never company rate (source has both paths)
From KB-093: "Prior audits checked 'does code exist' not 'does logic match'. Schema presence != behavior parity."
---
Why This Is a Model Behavioral Issue (Not Application Logic)
- Persists despite explicit instructions: CLAUDE.md contains
MUST-VERIFY: NEVER write column/table/variable names from memory. Read source first.-- added after KB-007. Claude continued hallucinating in KB-013, KB-015, KB-057, KB-093.
- Persists despite Known Bugs reference: KB-005 documented the correct column name. Claude wrote the wrong name in 3 more files afterward (KB-013). The KB file was available and loaded in context.
- Persists despite a 10-point pre-change checklist: Item #1 is "Names -- Is every column, table, variable, method name verified from source?" -- created specifically for this pattern. Claude continued hallucinating after this rule was added.
- The same hallucination recurs across sessions:
voucher_date(wrong) vsdate(correct) appears in KB-005, KB-007, KB-013, KB-015 -- four separate entries for the same hallucinated name, across different files and sessions.
- Source files are available and short: The model files that define the correct names are typically 20-80 lines. Claude could read them in <1 second. It chooses to generate from internal knowledge instead.
---
Quantified Impact
| Metric | Value |
|---|---|
| Total documented KB entries for this pattern | 7 (KB-007, 008, 010, 013, 014, 015, 057) |
| Total column name mismatches found | 23+ (KB-057 alone) |
| Files affected | 15+ across services, routes, frontend, infrastructure |
| Detection method | Manual audit and runtime crashes (not caught by linting or compilation) |
| Time to discover | Each batch: 1-3 days after code was written |
| Rules created to prevent | 3 (MUST-VERIFY in CLAUDE.md, pre-change-checklist item #1, KB entries) |
| Rules effective at preventing recurrence | 0 (pattern continued after all 3 rules were added) |
---
Suggested Improvements
1. Mandatory source read before identifier use
When Claude writes code that references a column, field, or method from another file, it should read that file first -- even if it "remembers" the name from earlier in the session. The cost is one Read tool call (~0.1s). The savings is avoiding a runtime crash.
Specific trigger: Any time Claude writes model.field_name, table.column_name, or calls module.function_name(), verify the identifier exists by reading the definition file. This is especially critical for:
- ORM model field access
- Raw SQL column references
- API request/response body fields
- Environment variable names
2. Project-wide grep after fixing a name error
When Claude fixes a wrong column/field name in one file, it should grep the project for the same wrong name in all other files. KB-013 documents that fixing voucher_date in one file left it broken in 3 others.
3. Cross-file contract verification
When writing frontend code that calls a backend endpoint, or vice versa, Claude should read both sides and verify the contract matches:
- Request body shape
- Response envelope structure
- URL path and method
- Environment variable names (producer and consumer)
4. Distinguish "I remember" from "I verified"
Claude should internally distinguish between "I believe this column is called X" (memory) and "I read the model and it is called X" (verified). Only verified names should appear in generated code. If Claude catches itself about to write a name from memory, it should pause and read the source.
5. Intelligence/raw-SQL table awareness
For tables without ORM models (created via raw SQL CREATE TABLE), Claude should be especially cautious since there is no type system to catch errors. The builder SQL is the only source of truth, and Claude should read it before writing any query against the table.
---
Environment
- Tool: Claude Code CLI + VS Code extension
- Models observed: Claude Opus and Sonnet (pattern consistent across both)
- Project scale: ~90K LOC migration, 31 ORM models, 52 raw-SQL intelligence tables, 68+ API endpoints
- Duration: ~30 sessions, 93 documented bugs (KB-001 through KB-093)
- Instructions quality: 400+ line CLAUDE.md with explicit MUST-VERIFY rule, 10-point pre-change checklist, and 7 KB entries documenting this exact pattern -- none prevented recurrence
- Source files available: All model/migration/builder files were in the project, typically 20-80 lines, trivially readable
Reproduction
- Create a project with 10+ ORM models and some raw SQL tables (no ORM)
- Ask Claude to write service methods that query these tables
- Do NOT pre-read the models for Claude -- let it write from its own knowledge
- Check the generated column names against actual model definitions
- You will find hallucinated names, especially for columns with common-sounding alternatives (
datevsvoucher_date,namevsledger_name, boolean flags vs enum strings) - Add a rule saying "never write column names from memory" and repeat -- the behavior will continue
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗