Skip to content

fix(store): keep uninit residue and its tombstone for an explicit init - #335

Merged
sunerpy merged 6 commits into
mainfrom
fix/older-uninit-residue
Oct 22, 2026
Merged

sunerpy merged 6 commits into
mainfrom
fix/older-uninit-residue

Conversation

@sunerpy

@sunerpy sunerpy commented Oct 22, 2026

Copy link
Copy Markdown
Owner

Summary

Four lifecycle defects around the tombstone uninit --force writes, all reproduced on the released v0.55.1 binary (the first with v0.54.0 residue). One commit each, each with a failing test first.

  1. An older version's uninit read as outdated (cd0454a). The state gate compared extraction versions before phases, so sync (and a no-daemon MCP catch-up) rebuilt an index the user removed, under its tombstone; afterwards status and every read failed with "blocked by the uninitialized tombstone". An older uninitialized phase now classifies Uninitialized, like this version's own residue.
  2. uninit --force refused an index an older version built (48275cf): "uninit is not authorized for index state outdated", so removal took a full sync first. Uninit now accepts Outdated (gate, write purpose, and the Outdated -> Uninitialized publisher transition).
  3. Rebuilds under the tombstone (5468537). After an uninit, an interrupted init leaves building beside the tombstone; the read error recommended index --force, which rebuilt everything (871 files, 14 s) and left Current under the tombstone, unreadable until another init. A reindex and the sync migration now refuse a tombstoned namespace under their exclusive lease before publishing anything, and recovery guidance names codegraph init while the tombstone exists.
  4. status failed on Current + tombstone (2e844c4). It now reports the degraded diagnostic it already gives for a leftover SQLite sidecar, with recoveryCommand: codegraph init <project>; the read error names codegraph init too.

docs/cli.md: the upgrade section, a new "After uninit --force" section, and the status diagnostics.

No graph, schema or extraction-version change.

Testing

  • New tests (CLI batch_m_outdated_migration, store index_state, index_state_publisher, rebuild) failed on the unfixed code for the expected reason; every fix piece was disabled one at a time and its guard failed each time.
  • Expectations that pinned the old behavior were updated, none removed: the store's older-version mapping, the cold-daemon older-uninit test, the store write-purpose matrix, and the publisher transition lists.
  • make pre-ci passed in the pre-push hook on the pushed head.

BEGIN_COMMIT_OVERRIDE
fix(store): an older version's uninit stays uninitialized
fix(store): uninit removes an index an older version built
fix(store): only an explicit init rebuilds under the uninit tombstone
fix(cli): status reports a current index under the uninit tombstone
END_COMMIT_OVERRIDE

`uninit --force` leaves an `uninitialized` state slot, its tombstone and no
database. The state gate compared extraction versions before phases, so the
residue an older version left read as `outdated`: `status` said so, and
`sync` (like the catch-up a CODEGRAPH_NO_DAEMON=1 session runs) rebuilt the
index under the tombstone. Every later read, `status` included, then failed
with "Current index state is blocked by the uninitialized tombstone" until an
explicit `init`. Reproduced with the released v0.54.0 and v0.55.1 binaries.

An older `uninitialized` phase now classifies as `Uninitialized`, as this
version's own residue does, so `sync`, `index --force` and the daemon refuse
it and only `init` rebuilds it. The `building` and `current` phases an older
version wrote stay `Outdated`.

Two tests pinned the old classification: the store's older-version mapping
and the cold daemon test for an older uninit. Both now expect
`uninitialized`; the daemon test also checks that the daemon refuses it as
uninitialized and never attempts an upgrade.
`uninit --force` refused an `outdated` index ("uninit is not authorized for
index state outdated"), so removing an index an older version built took a
full `sync` rebuild first. Uninit reads no row of the index it removes, so it
now accepts an outdated namespace: the uninit gate and its write purpose
admit `Outdated`, and the state publisher allows `Outdated -> Uninitialized`.
The uninitialized slot is published at this extraction version, after which
the tombstone and the database go as for any other index.

The store gate matrix and the publisher's invalid-transition list pinned the
refusal; both now expect the transition, and a publisher test checks the
slot it writes.
Only an explicit `init` removes the tombstone `uninit --force` writes, but a
reindex or the migration a `sync` escalates to rebuilt a namespace that still
held it. An `init` that follows an uninit and is interrupted after publishing
`building` showed the trap: the read error recommended `index --force`, which
rebuilt the whole index (871 files, 14 s on the released v0.55.1) and left
Current beside the tombstone, so `status` and every read failed with "Current
index state is blocked by the uninitialized tombstone" until another `init`.
An index an older version built that still carries a tombstone hit the same
path through `sync`.

Both rebuild entry points now refuse a tombstoned namespace under their
exclusive lease, before `phase=building` is published, with
`TombstoneRequiresExplicitInit`; a tombstone path that cannot be inspected
fails closed. While the tombstone exists, `status` (human and
`recoveryCommand`), the read errors for a `building` or `outdated` index, and
the note after a failed build name `codegraph init` instead of `index --force`.
docs/cli.md gains an "After `uninit --force`" section.
A Current index beside the uninit tombstone, which an `init` that published
Current but could not remove the tombstone leaves, made `codegraph status`
exit with "Current index state is blocked by the uninitialized tombstone",
although docs/cli.md sends every lifecycle question to `status`. It now
reports the degraded diagnostic it already gives for a leftover SQLite
sidecar: `initialized: false`, `extractionStatus: "current"`, the refusal in
`extractionStatusDetail`, plus `recoveryCommand: codegraph init <project>`;
human output says `current (blocked by the uninit tombstone)` and names the
recovery. The refusal itself now ends with "run `codegraph init` to rebuild
the index", so every read error names the recovery too.
@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.43066% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/codegraph-cli/src/main.rs 91.76% 7 Missing ⚠️
crates/codegraph-store/src/rebuild.rs 95.83% 2 Missing ⚠️

❌ Your patch check has failed because the patch coverage (93.43%) is below the target coverage (95.00%). You can increase the patch coverage or adjust the target coverage.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #335      +/-   ##
==========================================
- Coverage   95.59%   95.59%   -0.01%     
==========================================
  Files         280      280              
  Lines      166400   166519     +119     
==========================================
+ Hits       159077   159183     +106     
- Misses       7323     7336      +13     
Files with missing lines Coverage Δ
crates/codegraph-store/src/connection.rs 88.22% <ø> (ø)
crates/codegraph-store/src/index_state.rs 83.41% <100.00%> (+0.07%) ⬆️
...rates/codegraph-store/src/index_state_publisher.rs 89.94% <100.00%> (+0.01%) ⬆️
crates/codegraph-store/src/uninit.rs 94.14% <ø> (ø)
crates/codegraph-watch/src/sync.rs 98.25% <ø> (ø)
crates/codegraph-store/src/rebuild.rs 93.08% <95.83%> (+0.10%) ⬆️
crates/codegraph-cli/src/main.rs 91.49% <91.76%> (-0.07%) ⬇️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…tone

An index an older version built that still carries the uninit tombstone is
refused by `sync` since the previous commit, but `status` still recommended
`codegraph sync` and gave no `recoveryCommand`. While the tombstone exists it
now names `codegraph init <project>` in both outputs, as it does for an
interrupted build.
@sunerpy
sunerpy merged commit 139fb0a into main Oct 22, 2026
11 checks passed
@sunerpy
sunerpy deleted the fix/older-uninit-residue branch October 22, 2026 17:06
sunerpy added a commit that referenced this pull request Oct 23, 2026
A new dated audit file, docs/upstream-sync/POST_V1_6_2_MAIN_2026-10-22.md,
classifies the 225 colby `main` commits after `b635dd46` (to `e8ddc4e2`,
2026-10-22; no new upstream tag) against v0.55.1: 147 PORT, 36 PARTIAL,
21 DEFER, 10 ALREADY-HAVE, 10 N/A and one KEEP-RUST, with the owner's
decisions of 2026-10-22 (the follow-up pull request takes the DEFER rows; the
older gaps become G18 to G25; the C lone-macro KEEP-RUST rule is reopened;
W7-14 builds jsx-render on the tree-based JSX references). Two rows marked
ALREADY-HAVE failed part of their upstream test's own fixture and are
PARTIAL.

UPSTREAM.md gains the audit entry, the #335 entry and the v0.55.2 release
record with its black-box acceptance, and its Current alignment moves the
discovery boundary to `e8ddc4e2`; docs-check follows the new boundary.
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.

1 participant