Skip to content

Failed graph-ingest mutation receipt fail-closes change.detect repo-wide with no reconcile path; reindex does not clear it #692

Description

@tiendungdev

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

  1. 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).

  2. 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.

  3. 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.

  4. 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

  1. 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.
  2. Automatic ingest retry with backoff for failed heads once load drains, instead of requiring a caller-initiated superseding mutation.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions