fix(knowledge): the code↔DB map invented 33 of the 39 tables it named - #234
Merged
Conversation
This is the product's core promise for a repository added to a dashboard: the agent goes
to the code and knows how it uses the data. Measured in production on 2026-08-26 against
the one real customer project — a Laravel repository, 213-table MySQL — the map named 39
tables, of which SIX exist.
real catalog_phone_numbers purchases unlimit_wallets sentry_samplers
coverage_rule_import_exceptions partner_phone_number_tests
noise the to if an a get one ever every group its ends cascade
current_timestamp 31 any ases whatever
bs ls ns os pts res rs ses sts us xts zes
All three rows surfaced as sync_status='mismatch' — the headline signal, "your code says
one thing and your database says another" — were noise: tables `any`, `ases`, `whatever`,
columns `e` and `now`. A customer opening that screen was told their code used a table
called `whatever`.
Two mechanisms, compounding. TABLE_REF_SQL matches FROM|JOIN|INTO|UPDATE|TABLE followed
by a word, and prose does that ("update TO the latest", "insert INTO IF needed"), as do
keywords that legitimately follow (ON DELETE CASCADE); six SQL keywords were the only
filter. Then `_model_name_to_table` pluralised whatever it was handed — z→zes, s→ses,
pt→pts — and it was handed fragments because `_extract_model_names` takes EVERY
`class \w+` in a non-Python file while ORM_PATTERNS["sqlalchemy"] matches the bare word
`Column` anywhere, so generated PHP reached the ORM branch and each class became a model.
- is_plausible_table_name() guards every recording site. Four shape rules; `ases` is `as`
pluralised and `ends` is `end`, so EVERY reading is checked rather than the first that
matches — `ends` parses as en+ds and as end+s, and taking the first let it through
- shape, not a blocklist: a list of the 33 observed words would pass that repository and
fail the next
- `_model_name_to_table` returns "" rather than a guess it cannot stand behind
- measured on the production shapes: the SQL path keeps 4 of 10 and they are exactly the
four real ones; the pluraliser invents 0 of the 14 it used to
Second half — a migration STATES its table:
- the previous parser found NOTHING in a Laravel repository. It required whitespace
before the name, so it missed `Schema::create(`, missed `op.create_table("x")` because
a paren is not whitespace, and missed `create_table :x` because `:` is not `\w`
- `eloquent` was absent from ORM_PATTERNS entirely; Laravel reached the ORM branch only
by accident. It is first-class now, and `protected $table` outranks the plural for the
same reason `__tablename__` does
- a declaration carries more authority than a guess, which makes a false declaration
worse — so migration output passes the same shape test
36 tests across two files, every fixture a real name from that repository.
Suite: 6912 passed, 4 skipped, 1 xfailed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtEZboEDbEXLYrLf1owRXB
…s-inventing-tables
This was referenced Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the product's core promise for a repository added to a dashboard: the agent goes to the code and knows how it uses the data. Measured in production on 2026-08-26 against the one real customer project — a Laravel repository against a 213-table MySQL database — the map named 39 tables, of which six exist.
All three rows surfaced as
sync_status='mismatch'— the headline signal, your code says one thing and your database says another — were noise: tablesany,ases,whatever, columnseandnow.A customer opening that screen was told their code used a table called
whatever.Two mechanisms, compounding
TABLE_REF_SQLmatchesFROM|JOIN|INTO|UPDATE|TABLEfollowed by a word — and prose does that ("update to the latest", "insert into if needed"), as do keywords that legitimately follow (ON DELETE CASCADE). Six SQL keywords were the only filter.Then
_model_name_to_tablepluralised whatever it was handed:z→zes,s→ses,pt→pts. Every one of the twelve suffix fragments is that function applied to a one- or two-character token — and it was handed them because_extract_model_namestakes everyclass \w+in a non-Python file whileORM_PATTERNS["sqlalchemy"]matches the bare wordColumnanywhere, so generated PHP reached the ORM branch and each of its classes became a "model".The guard
is_plausible_table_name()on every recording site. Four shape rules — length ≥ 3, starts with a letter, not a keyword or common word, not a bare plural suffix on a fragment or a keyword:asesisaspluralised,endsisend.Every reading is checked, not the first that matches.
endsparses asen+dsand asend+s; taking whichever ending matched first let it through, and that was a real bug in my first attempt.Deliberately shape, not a blocklist — a list of the 33 words that appeared would pass that repository and fail the next.
Measured on the production shapes:
Second half: a migration states its table
The previous migration parser found nothing in a Laravel repository. It was
(?:create_table|CreateTable|CREATE TABLE)\s+['"]?(\w+)— whitespace required before the name — so it missedSchema::create(entirely, missedop.create_table("x")because a paren is not whitespace, and missedcreate_table :xbecause:is not\w`. The map fell back to inference for every table the app has.eloquentwas absent fromORM_PATTERNS. Laravel reached the ORM branch only by accident. It is a first-class entry now, andprotected $table = '…'outranks the pluralised class name for the same reason__tablename__does.tables_declared_in_migration()reads Laravel, Rails, Alembic, raw DDL and Django. A declaration carries more authority than a guess, which makes a false one worse — so its output passes the same shape test.Tests
36 across two files, every fixture a real name from that repository rather than an invented one — including the six that were right, which the guard must not remove.
Suite:
6912 passed, 4 skipped, 1 xfailed.Note on CI:
mainis unprotected and GitHub has not dispatched a workflow since 2026-08-26T00:30Z (audit findings A-6 / A-7), so this PR may show no checks through no fault of its own. The suite above was run locally in full.🤖 Generated with Claude Code
https://claude.ai/code/session_01XtEZboEDbEXLYrLf1owRXB