You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(rescan): retain modTimes when purge emit fails (#210)
## Summary
Fixes#207 — a rescan tick that detected a file is gone deleted its
entry from `r.modTimes` **before** `reconcileDeleted` actually emitted
the purge snapshot. If `emitter.Emit` then failed (SQLITE_BUSY
exhausting `Tx` retry budget, context cancellation racing the emit,
transient I/O), the in-memory tracking was already cleared but the DB
rows remained. The deletion was never retried, leaving zombie nodes that
surfaced via `MemorySearch`, snapshot replays, etc. — until a full
re-compile.
**Fix (issue option a — defer the delete):** mirror the discipline
already present in the compile path at `rescan.go:334`
(`maps.Copy(r.modTimes, pending)` runs *after* `compiler.Compile`
returns no error). Collect `(absPath, relPath)` pairs without mutating
`r.modTimes` in the loop; clear `r.modTimes` only when
`reconcileDeleted` returns `ok=true`.
- `reconcileDeleted` signature: `[]PurgedFile` → `([]PurgedFile, bool)`.
`ok=true` for the three no-op success branches (empty deleted, no
matching nodes, happy path); `ok=false` for `GetNodesByFiles` and `Emit`
errors.
- `notifyChange()` gated on `ok && len(deleted) > 0` — failed purges
don't fire change events.
- Bool over `error` per `.claude/rules/go-concise.md` §5 ("handle or
return, never both"): caller doesn't discriminate failure modes, and
`reconcileDeleted` already logs the underlying error internally.
## Test plan
- [x] `TestRescanLoop_RetainsModTimesOnPurgeEmitFailure` — new; mirrors
sibling `TestRescanLoop_SkipsPurgeOnWalkError`. Injects `r.walkFn` to
wrap the real walk + cancel ctx after the walk returns, so
`emitter.Emit`'s `BeginTx(ctx, ...)` fails. Asserts modTimes preserved +
DB nodes intact + no new snapshot row. Then resets `walkFn` and re-scans
to verify the retry path completes the purge.
- [x] `TestRescanLoop_ReconcilesDeletedFiles`,
`TestRescanLoop_PublishesStatus`, `TestRescanLoop_SkipsPurgeOnWalkError`
— still green (no regression in happy / walk-error paths).
- [x] `make test`, `make fmt lint tidy` — all green (0 lint issues).
Reviewed by `go-style-reviewer` (project subagent) + Codex (generic, via
`/forge ... codex`). Loop converged on first pass — zero actionable
findings.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments