Summary
A file mutation whose disk write succeeded but whose graph ingest failed (context deadline exceeded) leaves a graph_status: failed head in the mutation ledger. That head fail-closes change(operation="detect") for the entire repo, from every MCP session, and there is currently no operation that clears or reconciles it:
- full reindex and scoped
workspace_admin.reindex(paths=[...]) complete but do not clear the failed head;
- daemon restart does not clear it (the ledger persists in the store);
- a superseding mutation on the same path replaces the head, but its own ingest can fail again under load, re-locking detect;
- a sweep of the MCP facade (
change, edit, workspace_admin, session capabilities) and the CLI (gortex --help, gortex edit --help) found no clear/reconcile verb.
Related: #691 (deadline/cancellation asks overlap; this issue is about recovery, not latency).
Environment
- Gortex
v0.63.8+2a33f3d, Windows 11, native MCP.
- Single-repo workspace at the time of the detect refusals: 3,058 files, 148,117 nodes, 1,398,981 edges,
health_score=100, clean path liveness.
- Daemon under load during the re-failure: 15 connected MCP sessions, FTS doc count fluctuating (post-shrink store churn).
Sequence observed
-
edit_file mutation on a 15.7 KB bash script: disk_status: committed, graph_status: failed, receipt commit-3, caller mutation_id preserved. The bytes on disk are correct (verified by blob SHA).
-
Any change.detect (any scope, any session) now refuses:
change detection refused a stale graph: graph freshness unavailable:
failed receipt=mutation-3 repo="..." path="...\scripts\dev\....sh"
generation=6 error="context deadline exceeded"
Reproduced from a second, freshly oriented MCP session — the state is daemon-owned, not session-owned.
-
Full reindex (from the original session) and scoped workspace_admin.reindex(paths=[<file>]) both completed; change.receipt for the path still reports graph_status: failed.
-
Superseding byte-identical round trip: two edit_file mutations (with base_sha drift guard and physical_evidence), final content bit-identical to the pre-existing state. The detect refusal switched from failed generation=6 to pending generation=9/12 — so superseding works at the ledger level — but after ~10 minutes under load both new generations flipped to failed with context deadline exceeded again, re-locking detect.
Receipt surface observations
change.receipt by source.path is honest and useful (committed/pending/failed, byte counts, SHAs).
- The key printed in the detect refusal (
mutation-3) is an internal ledger key: querying it via options.mutation_id returns no mutation recorded for mutation_id mutation-3. The error text and the receipt API use different id spaces, which sent our tooling down a wrong path during triage.
Requested improvements
- A reconcile path for a failed ingest head: an explicit clear/reconcile operation (e.g. on
change.receipt or workspace_admin), or have a successful re-parse of the file during reindex clear the failed head — today reindex re-parses the file yet the head stays failed.
- Automatic ingest retry with backoff for
failed heads once load drains, instead of requiring a caller-initiated superseding mutation.
- Degrade instead of hard-refusing in detect: report the affected path(s) as a disclosed coverage gap (or accept an explicit
allow_stale option) rather than refusing the whole repo — one 15.7 KB file currently blocks change detection for a 3,000-file repo.
- Name a queryable id in the refusal: print the receipt id (
commit-N) or caller mutation_id rather than the internal ledger key.
Current workaround
None that fully recovers under load. Superseding the head works only if its ingest succeeds; until then agents fall back to targeted impact/tests/guards with known symbol ids and record the detect coverage gap. No raw pprof/source is attached.
Summary
A file mutation whose disk write succeeded but whose graph ingest failed (
context deadline exceeded) leaves agraph_status: failedhead in the mutation ledger. That head fail-closeschange(operation="detect")for the entire repo, from every MCP session, and there is currently no operation that clears or reconciles it:workspace_admin.reindex(paths=[...])complete but do not clear the failed head;change,edit,workspace_admin,sessioncapabilities) and the CLI (gortex --help,gortex edit --help) found no clear/reconcile verb.Related: #691 (deadline/cancellation asks overlap; this issue is about recovery, not latency).
Environment
v0.63.8+2a33f3d, Windows 11, native MCP.health_score=100, clean path liveness.Sequence observed
edit_filemutation on a 15.7 KB bash script:disk_status: committed,graph_status: failed, receiptcommit-3, callermutation_idpreserved. The bytes on disk are correct (verified by blob SHA).Any
change.detect(any scope, any session) now refuses:Reproduced from a second, freshly oriented MCP session — the state is daemon-owned, not session-owned.
Full reindex (from the original session) and scoped
workspace_admin.reindex(paths=[<file>])both completed;change.receiptfor the path still reportsgraph_status: failed.Superseding byte-identical round trip: two
edit_filemutations (withbase_shadrift guard andphysical_evidence), final content bit-identical to the pre-existing state. The detect refusal switched fromfailed generation=6topending generation=9/12— so superseding works at the ledger level — but after ~10 minutes under load both new generations flipped tofailedwithcontext deadline exceededagain, re-locking detect.Receipt surface observations
change.receiptbysource.pathis honest and useful (committed/pending/failed, byte counts, SHAs).mutation-3) is an internal ledger key: querying it viaoptions.mutation_idreturnsno mutation recorded for mutation_id mutation-3. The error text and the receipt API use different id spaces, which sent our tooling down a wrong path during triage.Requested improvements
change.receiptorworkspace_admin), or have a successful re-parse of the file during reindex clear the failed head — today reindex re-parses the file yet the head stays failed.failedheads once load drains, instead of requiring a caller-initiated superseding mutation.allow_staleoption) rather than refusing the whole repo — one 15.7 KB file currently blocks change detection for a 3,000-file repo.commit-N) or callermutation_idrather than the internal ledger key.Current workaround
None that fully recovers under load. Superseding the head works only if its ingest succeeds; until then agents fall back to targeted impact/tests/guards with known symbol ids and record the detect coverage gap. No raw pprof/source is attached.