Skip to content

fix: index --full FK crash (#16) + cross-process contextfit ingest lock (#17) - #20

Merged
owrede merged 3 commits into
mainfrom
fix/issues-16-17-contextfit-ingest
Jul 2, 2026
Merged

fix: index --full FK crash (#16) + cross-process contextfit ingest lock (#17)#20
owrede merged 3 commits into
mainfrom
fix/issues-16-17-contextfit-ingest

Conversation

@owrede

@owrede owrede commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Fixes two open bugs in the ContextFit backend.

#16index --full fails with FOREIGN KEY constraint failed

The full-mode wipe loop deleted chunks while sections.chunk_id_first/last
(REFERENCES chunks(id), no ON DELETE) still pointed at them. The per-note
re-index path already ordered these correctly; only the full-wipe path missed it.
Fix: delete sections before chunks in the wipe loop + regression test.

#17 — concurrent contextfit ingests corrupt the KB

A CLI index running alongside a serve-side debounced re-ingest (or a second
stale serve) had both contextfit ingest calls rm/rewrite the same KB dir;
the loser crashed mid-write, sometimes leaving a half-written KB.

Fix: a dedicated per-vault mutex locks/<vault>.ingest.lock (separate from
the brief daemon's lifetime-held <vault>.lock) wrapped around the single ingest
chokepoint (indexVaultWithContextFit), covering all four call sites (CLI index,
serve write-refresh, watcher re-ingest, startup catch-up). Contention policy is
skip + persisted .dirty flag: a second-comer marks dirty and returns
immediately (no wait, no wasted double-rebuild, new status "skipped"); the lock
holder does one trailing re-ingest if a write lands mid-run, so the latest change
is never lost across processes. Startup catch-up honors a crash-stranded flag.

Out of scope (not bugs in this repo)

Verification

  • Tests: 1762 passed (+15 new), 11 skipped, 0 failures, 146 files.
  • tsc --noEmit clean · adapter-seam lint green · prettier clean.

Closes #16
Closes #17

🤖 Generated with Claude Code

owrede and others added 3 commits July 2, 2026 12:59
`index --full` failed with `FOREIGN KEY constraint failed` on any vault
with a populated `sections` table. The full-mode wipe loop deleted chunks
while `sections.chunk_id_first/last` (REFERENCES chunks(id), no ON DELETE)
still pointed at them. The per-note re-index path already ordered these
correctly; only the full-wipe path missed it.

Add `sections.deleteByNote(n.id)` before `chunks.deleteByNote(n.id)`,
mirroring the per-note path and single.ts step 7. Add a regression test
that asserts the wrong order trips the FK and the corrected order does not.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Concurrent ContextFit KB ingests were not mutually excluded across
processes. A CLI `index` running while a serve process fired its
debounced re-ingest (or a second stale serve) had both `contextfit
ingest` calls `rm` and rewrite the same KB dir; the loser crashed
mid-write (`FileNotFoundError: .../chunks/index.json.tmp`), sometimes
leaving a half-written KB. The existing `<vault>.lock` (brief daemon,
held for its whole lifetime) can't serialize this — reusing it would make
every ingest skip forever.

Add a dedicated per-vault mutex `locks/<vault>.ingest.lock` (atomic `wx`
create, PID-based steal-on-dead, mirroring brief/lock.ts) wrapped inside
`indexVaultWithContextFit`, the single chokepoint for all four call sites
(CLI index, serve write-refresh, watcher re-ingest, startup catch-up).

Contention policy is SKIP + a persisted `<vault>.ingest.dirty` flag: a
second-comer marks the vault dirty and returns immediately (no wait, no
wasted double-rebuild) with new status "skipped" (treated as success by
callers). The lock holder clears the flag before each pass and does one
trailing re-ingest if a write set it mid-run, so the latest change is
never lost even across processes; MAX_PASSES bounds churn. Startup
catch-up honors a leftover dirty flag so a crash-stranded flag is cleaned
up at the next boot.

Adds ingest-lock.ts + 14 tests (lock primitive: acquire/steal/release,
dirty round-trip; orchestration: skip-and-flag, single ingest, trailing
re-ingest, lock-released-on-throw, MAX_PASSES backstop). Callers updated
to log "skipped" benignly rather than as an error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@owrede
owrede merged commit 12182cd into main Jul 2, 2026
2 checks passed
@owrede
owrede deleted the fix/issues-16-17-contextfit-ingest branch July 2, 2026 20:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant