Skip to content

fix(graph): invalidate cached context after atomic file replacement - #577

Merged
shhdwi merged 1 commit into
trailhq:mainfrom
Frankie-Xu:fix/graph-cache-file-identity-20261009
Nov 8, 2026
Merged

shhdwi merged 1 commit into
trailhq:mainfrom
Frankie-Xu:fix/graph-cache-file-identity-20261009

Conversation

@Frankie-Xu

@Frankie-Xu Frankie-Xu commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

When a long-lived process has cached the graph or ask sidecar, an external atomic replacement with the same file size and mtime leaves the old parsed value cached. Repairing malformed JSON by such a replacement can also keep returning cached null. Both uncached production readers already see the replacement bytes in the regressions.

Extend the existing file stamp with device, inode and ctime. The next load observes the replacement, while subsequent unchanged loads retain the shared parsed object. Public loader interfaces, null semantics, per-path isolation and explicit invalidation remain unchanged. This fixes stale Agent context after file replacement; it does not provide a content-hash guarantee for in-place writes whose entire observed stamp is unchanged.

Validation: the exact complete source closure on main fe30ead39d5e6f0c921018d364da2bdbc9d4b3ad is unchanged from the independently reviewed baseline. The observed baseline has 6 passes and 4 causal failures, and the same observed regression with the fix has 10/10 passes. The final upstream regression has 10/10 on both the original candidate and the actual main-based isolated checkout (macOS arm64, Node26.5.0, one worker). Tests use real production writers/readers and filesystem rename, including same-mtime/size replacement, malformed JSON repair, warm reuse, missing/removal/recreation, invalidation and root isolation. Linux/Windows replay entry points are prepared but have not run. No local full-suite, MCP end-to-end or performance result is claimed in this local validation.

This is independent of #561 root routing and #399 ingestion. A read-only file-list review of all119 public open PRs at collection time found no change to the loader or this new test; this does not rule out unpublished work. Developed with AI assistance; patch, causal evidence and independent review are preserved locally.

Candidate commit: 3e3885ea430d77015bc676059ed63359b6cb7771; its two source/test files are the exact reviewed and locally validated patch.

Hosted validation and shared CI blocker (2026-10-09):

The Ubuntu test job passed on this PR source: 1366 passed, 0 failed, 1 skipped. All 10 tests in the changed regression file appear as passing in the hosted log. Node 24 WASM regression also passed. This is hosted Ubuntu evidence; the separate finite platform replay entry has not run, and no local Windows or extra Git-version matrix is claimed.

CodeQL analyze fails before scanning because initialization uses4.37.8 while analysis uses4.38.1 (Loaded a configuration file for version ...). The existing independent #562 tracks that workflow pin mismatch. No workflow patch, rerun or unrelated CI change is included here.

Hosted Windows validation (2026-10-09): the Windows test job also passed: 1361 passed, 0 failed, 6 skipped. All 10 cases in this PR's changed regression file match passing TAP entries on Windows, including the actual filesystem/Git assertions. This adds hosted platform evidence; the separate baseline/candidate replay entry and extra Git-version matrix remain unexecuted.

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

🌱 graft blast radius

1 area changed → 9 areas can be affected. 16 dependent symbols, depth 2.
Tests: no test reaches Graph Loading.
Tag: @Bortlesboat — 3 of 10 areas

flowchart TB
  A0(("Command Line Interface<br/>3 symbols"))
  A1(("Graph Refreshing<br/>3 symbols"))
  A2(("Tool Invocation<br/>2 symbols"))
  A3(("Corpus Loading<br/>2 symbols"))
  A4(("Cloud Onboarding<br/>2 symbols"))
  AX(("4 smaller areas<br/>4 symbols"))
  classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
  class A0,A1,A2,A3,A4 reached;
  classDef tail fill:#EEF2F3,stroke:#9AA4A9,stroke-width:1px,color:#3A4247;
  class AX tail;
Loading
Can be affected Symbols Nearest hop Reached from
Command Line Interface 3 src/cli.ts:L1-L3312 cli.ts — calls, depth 2 Graph Loading
Graph Refreshing 3 src/graph/refresh.ts:L271-L347 ensureFreshGraph — calls, depth 2 Graph Loading
Tool Invocation 2 src/mcp/tools.ts:L265-L351 callSingleTool — calls, depth 2 Graph Loading
Corpus Loading 2 src/ask/ask.ts:L165-L190 loadCorpus — calls, depth 2 Graph Loading
Cloud Onboarding 2 src/cloud/onboarding.ts:L458-L473 tokensOnLastPr — calls, depth 2 Graph Loading
Team Check 1 src/notes/team-check.ts:L31-L61 codeAnchors — calls, depth 2 Graph Loading
3 smaller areas 3 Pull Request Review, Blast Command, Grep Command see below
Who knows this code — 1 person across 10 areas
Area Who knows it
Command Line Interface · affected @Bortlesboat — 1 commit, last yesterday
Graph Refreshing · affected @Bortlesboat — 1 commit, last yesterday
Tool Invocation · affected @Bortlesboat — 1 commit, last yesterday
Graph Loading · changed only you — nobody else has touched these files
Corpus Loading · affected only you — nobody else has touched these files
Cloud Onboarding · affected only you — nobody else has touched these files
Team Check · affected only you — nobody else has touched these files
Pull Request Review · affected only you — nobody else has touched these files
…2 further areas

Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no @ has no GitHub handle in its commit email — tag them by hand, or add a .mailmap entry. A suggestion from history, not a CODEOWNERS rule.

All 16 dependent symbols, grouped by area

Command Line Interface — 3 symbols in 1 file

  • src/cli.ts:L1-L3312 — cli.ts (calls, depth 2)
  • src/cli.ts:L1551-L1996 — runInitCommand (calls, depth 2)
  • src/cli.ts:L3200-L3261 — statusCommand (calls, depth 2)

Graph Refreshing — 3 symbols in 3 files

  • src/graph/refresh.ts:L271-L347 — ensureFreshGraph (calls, depth 2)
  • src/graph/traverse-cli.ts:L167-L243 — runCallersCommand (calls, depth 2)
  • src/graph/workspace.ts:L146-L157 — loadWorkspaceGraphs (calls, depth 2)

Tool Invocation — 2 symbols in 1 file

  • src/mcp/tools.ts:L265-L351 — callSingleTool (calls, depth 2)
  • src/mcp/tools.ts:L211-L220 — extensionScopeNote (calls, depth 2)

Corpus Loading — 2 symbols in 1 file

  • src/ask/ask.ts:L165-L190 — loadCorpus (calls, depth 2)
  • src/ask/ask.ts:L1440-L1470 — skeleton (calls, depth 2)

Cloud Onboarding — 2 symbols in 1 file

  • src/cloud/onboarding.ts:L458-L473 — tokensOnLastPr (calls, depth 2)
  • src/cloud/onboarding.ts:L381-L407 — tokensOnOneLookup (calls, depth 2)

Team Check — 1 symbol in 1 file

  • src/notes/team-check.ts:L31-L61 — codeAnchors (calls, depth 2)

Pull Request Review — 1 symbol in 1 file

  • src/app/review.ts:L45-L99 — reviewPullRequest (calls, depth 2)

Blast Command — 1 symbol in 1 file

  • src/blast/blast-cli.ts:L64-L125 — runBlastCommand (calls, depth 2)

Grep Command — 1 symbol in 1 file

  • src/search/grep-cli.ts:L95-L127 — runGrepCommand (calls, depth 2)
Test signal per changed area — 1 ✗

Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.

  • ✗ Graph Loading — 0 of 2 reached · no test file reaches it
    • not reached: statOf, loadCached
4 test suites also reference this code

4 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.

  • test/graph-load.test.ts
  • test/graph-scopes.test.ts
  • test/regression-single-repo.test.ts
  • test/trail-connect.test.ts

graft blast · refs/graft/base...HEAD · depth 2 · 2 changed files

Open the interactive graph → — click an area to see the code that changed, and the line that reaches it.

github-actions Bot added a commit that referenced this pull request Oct 9, 2026
@shhdwi
shhdwi marked this pull request as ready for review November 8, 2026 10:43
@shhdwi
shhdwi merged commit 9051f30 into trailhq:main Nov 8, 2026
7 of 8 checks passed
github-actions Bot added a commit that referenced this pull request Nov 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants