fix(mcp): self-heal stale read-tool responses after external file edits - #180
Merged
Conversation
…-repo mode ensureFresh stood down entirely when a multiIndexer was present, so after a native edit or any external write the index-backed read tools (get_symbol_source, get_symbol, get_file_summary, get_editing_context, get_cfg) kept serving the old body until the watcher caught up. Route each path to its owning per-repo indexer and re-index it when IsTrackedStale reports drift, then restamp the recorded mtime so a follow-up read in the same window does no redundant work. IsTrackedStale is false for untracked/new/current files, so only a known-and-changed file is touched -- the accurate per-repo staleness check is what makes this safe, replacing the earlier cross-repo mass re-index that raced the live read surface.
When an overlay session is active, read_file serves the editor's in-buffer text over the on-disk file, previously with no signal -- so an agent could not tell a live buffer view from disk content. Stamp served_from:"overlay" plus an omission note when the bytes came from an overlay. A drifted overlay is still rejected upstream by the overlay view guard (read_file errors rather than serving a stale buffer); this only annotates the live-buffer case.
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.
Problem
After an external file change — a native
Edit/Write, agitcheckout, or any edit that does not go through Gortex's ownedit_file/write_file— the index-backed read tools (get_symbol_source,get_symbol,get_file_summary,get_editing_context,get_cfg) could return a stale body until the file watcher caught up. The visible symptom is a read returning an old symbol body while the file's metadata (e.g. line count) already reflects the new content.Root cause
These tools call
ensureFresh()to re-index a drifted file before serving it. ButensureFreshbailed out unconditionally whenever amultiIndexerwas present:So in multi-repo mode the on-read self-heal did nothing at all. It had been disabled because the staleness check keyed off the lone single-Indexer, whose mtime map is empty for cross-repo paths — making
IsStaletrue for every file, so the resulting mass re-index raced the live read surface and crashed the transport. It was switched off rather than fixed.Fix
1.
ensureFreshis now multi-repo-aware.Each path is routed to its owning per-repo indexer (
MultiIndexer.IndexerForFile), and re-indexed only whenIsTrackedStalereports genuine drift.IsTrackedStalereturns false for untracked / new / already-current files, so only a known-and-changed file is touched — no mass churn, which is what removes the original crash hazard. The recorded mtime is restamped after re-index so a follow-up read in the same window does no redundant work. Single-repo behaviour is unchanged (an active watcher still owns freshness, and the auto-refresh stands down rather than fight it).2.
read_fileflags overlay-served responses.When an editor-buffer overlay is active,
read_fileserves the buffer over disk; it now stampsserved_from: "overlay"(plus an omission note) so an agent can tell a live buffer view from disk content. Drifted overlays were already rejected upstream by the overlay view guard, so this only annotates the live-buffer case.Tests
TestEnsureFresh_MultiRepoSelfHealsStaleFile— the regression test: a file changed on disk after indexing is re-indexed on read in multi-repo mode. Fails on the pre-fix code (which returned nil).TestEnsureFresh_SingleRepoSelfHealsStaleFile— same for the single-repo path.TestReadFile_FreshOverlayIsServed/TestReadFile_OverlayDriftSurfacesError— overlay provenance flag + the existing drift protection.ensureFreshtests (watch-mode-skip, nil-indexer, rate-limit) still pass.Verification
go build -o /tmp/gortex-build ./cmd/gortex/(CGO) — clean.go test -race ./internal/mcp/— 2417 passed.golangci-lint/go vet— clean on the touched files.