Skip to content

fix(directory): a schema-less codegraph.db does not make a project initialized (#1895) - #1913

Closed
danusha2345 wants to merge 3 commits into
colbymchenry:mainfrom
danusha2345:fix/1895-schemaless-db-not-initialized
Closed

danusha2345 wants to merge 3 commits into
colbymchenry:mainfrom
danusha2345:fix/1895-schemaless-db-not-initialized

Conversation

@danusha2345

Copy link
Copy Markdown
Contributor

Symptom

isInitialized() treated any existing .codegraph/codegraph.db as an initialized project. An empty or zero-table file in an ancestor directory — what an interrupted codegraph init, a stray touch, or a never-populated ~/.codegraph/ leaves behind — therefore captured the upward walk of findNearestCodeGraphRoot / resolveProjectPath / resolveServerRoot for every project beneath it. The real index of a sub-project became unreachable, the #1606 sub-project down-scan never ran (resolveServerRoot short-circuits on the up-walk hit), and commands either operated on the broken file or failed opening it instead of showing the not-initialized guidance. Reproduction in #1895: an 8 KB zero-table db two directories above ws/ made the fully indexed ws/sub invisible to codegraph serve --mcp.

The new predicate

An initialized project is one whose db actually carries the codegraph schema. isInitialized() now probes, cheapest first:

  1. codegraph.db is a regular file of at least 100 bytes (the SQLite header size) — an empty file fails here;
  2. the first 16 bytes are the SQLite format 3\0 magic — one small read;
  3. a read-only node:sqlite open with a single sqlite_master lookup for the nodes table.

Step 3 is memoized per path + mtime + size, so hot callers (the prompt hook, MCP root resolution on every tool call) never reopen an unchanged db; a real index costs one stat after the first call. The handle is closed immediately so the probe never holds the db (Windows file locking); a db another process holds locked counts as live, not broken.

Every caller goes through the one predicate — CLI resolveProjectPath, findNearestCodeGraphRoot, findIndexedSubprojectRoots, resolveServerRoot (MCP session/engine/roots), planFrontload (prompt hook), ToolHandler's projectPath resolution — so the behavior is consistent everywhere. With the ancestor no longer captured, resolveServerRoot falls through to the down-scan and adopts the indexed sub-project as before.

createDirectory() uses the same predicate, so codegraph init in the directory holding the broken file repairs it in place (the schema is applied with CREATE ... IF NOT EXISTS) instead of refusing with "Already initialized"; the CLI warns about what it found. Nothing is deleted automatically.

Verification

New __tests__/schemaless-db-not-initialized.test.ts (10 tests):

  • isInitialized / findNearestCodeGraphRoot / resolveServerRoot / planFrontload reject an empty, a zero-table and a garbage codegraph.db;
  • a real index still resolves upward from a subdirectory (no regression);
  • the memo follows the file: a db that gains its schema flips to initialized;
  • (a) an empty db in the parent no longer hijacks an initialized child — codegraph status and codegraph explore run from the child resolve the child;
  • (a) the issue's exact reproduction — a workspace container under a schema-less ancestor adopts its indexed sub-project via the down-scan;
  • (b) a schema-less parent with an uninitialized child: the CLI prints the not-initialized guidance and the MCP handler returns success-shaped not-indexed guidance — never a SQLite error, never isError;
  • (d) codegraph init in the directory with the broken file succeeds and the db ends up with the nodes table.

__tests__/frontload-hook.test.ts faked an indexed project with an empty codegraph.db; its helper now creates a real one. Also run: cli-index-explicit-path, mcp-unindexed, mcp-roots, frontload-hook, index-command, security — all green; tsc --noEmit exit 0.

Fixes #1895

🤖 Generated with Claude Code

@danusha2345

Copy link
Copy Markdown
Contributor Author

Pushed 74ba791: the schema probe now fails open. Once the file passes the size gate and the SQLite format 3\0 header check it is a SQLite database, and only a successful sqlite_master query proving the nodes table is absent returns "not initialized". Any open/prepare error — locked, busy, a WAL db in a directory we cannot create -shm in (read-only checkout, mount, another user's tree), disk I/O — returns true, which is main's pre-existing behaviour for a database we cannot inspect. No error strings are matched. Memo unchanged; empty and schema-less files still return false, so codegraph init still repairs them and never touches a real db. Added a test that copies a WAL db into a .codegraph/ made read-only with chmod (skipped on win32 and as root).

@danusha2345

Copy link
Copy Markdown
Contributor Author

Follow-up from a review pass (738b35a): a codegraph.db holding bytes that are not SQLite at all was treated as schema-less, so init promised "rebuilding it" and then failed with SQLite's "file is not a database". Only an empty file or a SQLite database without the codegraph tables is rebuilt in place now; for anything else init exits 1, names the file and says to move or delete it — it never touches it. New test covers the empty-file repair and two non-SQLite files (4 KB and 5 bytes); it fails without the change.

inth3shadows added a commit to inth3shadows/codegraph that referenced this pull request Sep 26, 2026
…db-not-initialized @ 738b35a)

# Conflicts:
#	CHANGELOG.md
inth3shadows added a commit to inth3shadows/codegraph that referenced this pull request Sep 26, 2026
colbymchenry#1842's quiet-sync test skips the relaunch (CODEGRAPH_WASM_RELAUNCHED=1)
that adds --disable-warning=ExperimentalWarning. With colbymchenry#1913 merged the
directory probe loads node:sqlite on that path, so the SQLite warning
reached stderr and broke the one-line assertion. Pass the same flags.
@danusha2345
danusha2345 force-pushed the fix/1895-schemaless-db-not-initialized branch 2 times, most recently from fca9364 to 66d50ad Compare September 28, 2026 16:10
danusha2345 and others added 3 commits September 28, 2026 21:16
…itialized (colbymchenry#1895)

isInitialized() accepted any existing .codegraph/codegraph.db, so an empty or
table-less file in an ancestor directory (an interrupted init, a stray touch,
a never-populated ~/.codegraph/) captured the upward walk of
findNearestCodeGraphRoot / resolveProjectPath / resolveServerRoot for every
project beneath it: the real index of a sub-project became unreachable, the
sub-project down-scan never ran, and commands either operated on the broken
file or failed opening it instead of showing the not-initialized guidance.

An initialized project is now one whose db carries the schema: the probe is
gated by file size (>= the 100-byte SQLite header), then the header magic,
then a read-only node:sqlite open with a single sqlite_master lookup for the
`nodes` table, memoized per path + mtime + size so hot callers (the prompt
hook, MCP root resolution on every call) never reopen an unchanged db. The
handle is closed immediately so the probe never holds the db on Windows; a
locked/busy db counts as live.

createDirectory() uses the same predicate, so `codegraph init` in the
directory with the broken file repairs it in place (the schema is applied
with CREATE ... IF NOT EXISTS) instead of refusing with "Already
initialized"; the CLI says what it found. Nothing is deleted automatically.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nitialized

The schema probe failed CLOSED: a real WAL codegraph.db in a directory the
process cannot write (a read-only checkout, a mount, another user's tree)
made the read-only node:sqlite open throw — SQLite must create `-shm` to
open a WAL database — and isInitialized() reported the project as not
initialized, where main's file-exists check had been right.

The probe now fails OPEN. Once the size gate and the `SQLite format 3\0`
header have passed, the file is a SQLite database; only a SUCCESSFUL
sqlite_master query proving the `nodes` table absent may return false. Any
open/prepare error (locked, busy, cantopen, readonly, disk I/O) returns
true, the pre-existing behaviour for a database we cannot inspect. No error
strings are matched. The per path + mtime + size memo is unchanged, and
schema-less/empty files still return false, so `codegraph init` still
repairs them and never touches a real db.

Test: a WAL db copied into a `.codegraph/` made read-only with chmod stays
initialized — skipped on win32 and when running as root (root ignores mode
bits).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing a rebuild

Review found that a `.codegraph/codegraph.db` holding bytes that are not
SQLite at all counted as "schema-less", so `init` printed "rebuilding it"
and then failed with SQLite's "file is not a database". Only an empty file
or a SQLite database without the codegraph tables can be rebuilt in place;
`hasSchemalessDb` now means exactly that, and the new `hasForeignDbFile`
names the other case. `init` stops on it with exit 1, says which file it
is and to move or delete it, and never touches it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danusha2345
danusha2345 force-pushed the fix/1895-schemaless-db-not-initialized branch from 66d50ad to 9a91c52 Compare September 28, 2026 18:25
inth3shadows added a commit to inth3shadows/codegraph that referenced this pull request Sep 29, 2026
colbymchenry#1842's quiet-sync test skips the relaunch (CODEGRAPH_WASM_RELAUNCHED=1)
that adds --disable-warning=ExperimentalWarning. With colbymchenry#1913 merged the
directory probe loads node:sqlite on that path, so the SQLite warning
reached stderr and broke the one-line assertion. Pass the same flags.
inth3shadows added a commit to inth3shadows/codegraph that referenced this pull request Sep 29, 2026
Tree = upstream main + the open PRs that still merge cleanly: colbymchenry#1925, colbymchenry#1957,
colbymchenry#1962, colbymchenry#1955, colbymchenry#1965 and the current heads of colbymchenry#1842 and colbymchenry#1913. Upstream has
already carried colbymchenry#1969-colbymchenry#1971, colbymchenry#1082 and colbymchenry#1791.

Dropped until they are rebased onto upstream's resolver rewrite: colbymchenry#1704,
colbymchenry#1921, colbymchenry#1926 and colbymchenry#1935.
@colbymchenry

Copy link
Copy Markdown
Owner

Thanks @danusha2345 — this landed in #2083 with all your commits and authorship kept, rebased onto current main. I added one commit on top: the header check opened and closed its own descriptor on codegraph.db, and closing any descriptor on a SQLite file drops the process's POSIX locks, including those of the MCP server's open connection (the two owner-takeover tests in mcp-projectpath-lifecycle failed with disk I/O error). A single read-only SQLite probe now tells the schema, a schema-less database and a non-database (SQLITE_NOTADB) apart. Fixes #1895.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

isInitialized() accepts a schema-less codegraph.db, so one empty file in an ancestor makes a real index unreachable

2 participants