Fix indexed values going stale: module-edit fan-out misses and one-generation-old file content meta#5595
Fix indexed values going stale: module-edit fan-out misses and one-generation-old file content meta#5595habdelra wants to merge 8 commits into
Conversation
The Linear MCP server registers as 'Linear' (capitalized), so the existing lowercase mcp__linear__* allowlist entries never match its tools. Add capitalized twins for the approved read tools, plus the read-only GitHub MCP tools that remote sessions use for PR reads and the session-start index-cache fetch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UFceD1sbstj9M4K8xRL62R
itemsThatReference only scanned boxel_index_working and prerendered_html, so a dependent whose row exists only in production boxel_index (index imported from a production-tables-only dump, or rows staged under a since-changed schema) was never re-rendered when a module it depends on changed. Its indexed computed values stayed stale until the file was touched individually. Scan production too, excluding promoted tombstones, and collapse duplicates in the existing (url, type) dedup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UFceD1sbstj9M4K8xRL62R
A render that runs inside the very batch that re-indexes a file reads the pre-swap production index row, so a card linking the file indexed the file's previous contentHash/contentSize — and any reindex re-serialized the same stale values instead of healing them. realm_file_meta is written in the same critical section as the file's bytes, so prefer it when serving file-meta documents; the indexed values remain fallbacks for files never hashed at write time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UFceD1sbstj9M4K8xRL62R
An instance's initial index visit can expand a searchable FileDef link
as { id } only, so the baseline snapshot now comes from the fan-out
re-render that follows the file's first write — the same path the
rewrite assertion exercises. Also let QUNIT_FILTER narrow a run to
individual tests, complementing TEST_MODULES/TEST_FILES.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UFceD1sbstj9M4K8xRL62R
Preview deploymentsHost Test Results 1 files ±0 1 suites ±0 3h 1m 53s ⏱️ +19s Results for commit 3ecc57d. ± Comparison against earlier commit ec16ba0. Realm Server Test Results 1 files ± 0 1 suites ±0 13m 33s ⏱️ +13s Results for commit 3ecc57d. ± Comparison against earlier commit ec16ba0. |
State why realm_file_meta is authoritative and how the indexed values lag, rather than describing what would go wrong without the preference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UFceD1sbstj9M4K8xRL62R
There was a problem hiding this comment.
Pull request overview
[Claude Code 🤖] This PR fixes two correctness gaps that can leave indexed values stale: (1) invalidation fan-out missing dependents that only exist in the promoted index, and (2) file-meta serving returning one-generation-old contentHash/contentSize during same-batch rewrite + re-render scenarios. It also adds targeted tests to lock in the healed behavior.
Changes:
- Expand
itemsThatReferenceto scan promotedboxel_index(while excluding promoted tombstones) so module edits fan out to all promoted dependents, even when the working table is missing rows. - Serve FileMeta
contentHash/contentSizepreferentially fromrealm_file_meta(write-time values) rather than index-stored values that can lag during batch promotion. - Add/extend tests for the stale-index scenarios; add optional QUnit filtering via
QUNIT_FILTER.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/realm.ts | Prefer persisted write-time content meta over index-stored meta when serving FileMeta documents. |
| packages/runtime-common/index-writer.ts | Include promoted boxel_index in dependency fan-out scanning; filter out promoted tombstones. |
| packages/realm-server/tests/indexing-test.ts | Add regression tests covering same-batch rewrite + consumer re-render and FileMeta meta-source preference. |
| packages/realm-server/tests/index.ts | Add QUNIT_FILTER env var support to narrow QUnit runs. |
| packages/realm-server/tests/helpers/indexing.ts | Add helper to fetch and parse the production search_doc for assertions. |
| packages/host/tests/unit/index-writer-test.ts | Add unit test ensuring invalidations reach production-only dependents and skip promoted tombstones. |
| .claude/settings.json | Extend MCP allowlist entries (includes duplicated Linear entries with different casing). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
A whitespace-only value became an active filter matching nothing, silently running no tests. Trim and only apply when non-empty, matching the TEST_MODULES handling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UFceD1sbstj9M4K8xRL62R
The tools resolve as mcp__Linear__* in this environment; the lowercase mcp__linear__* twins never matched. Keep the capitalized set and drop the dead duplicates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UFceD1sbstj9M4K8xRL62R
The end-to-end full-reindex test passed with or without the file-meta serving change, so it never exercised the regression it claimed to guard. Bug #2 is guarded by the serving-precedence test, which fails without the fix. Remove the redundant test and its now-unused searchDocForIndexEntry helper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UFceD1sbstj9M4K8xRL62R
Indexed values could go stale in two ways that no reindex would heal:
A module edit never re-rendered some dependent instances. The invalidation fan-out (
itemsThatReference) scanned onlyboxel_index_workingandprerendered_html. The working table is a staging area whose coverage is not guaranteed — a row exists there only once a visit has written it in the deployment's lifetime, so promoted rows can lack a working counterpart (an index imported from a dump that carries only the production tables, or rows staged under a since-changed schema). Dependents in that state were invisible to the traversal: a module edit that adds or changes a computed field left their indexed computed values stale (nullfor a new field) until each instance file was touched individually.Rewriting a file's bytes left linked cards with the previous
contentSize/contentHash. When a consumer card is rendered,fileMetaDocumentFromIndexserved the linked file's content values from the file's index row — which, during a batch that re-indexes the file, is still the pre-swap production row (promotion happens only at batch completion). So the consumer serialized the previous bytes' values.Changes
itemsThatReferencenow also scans productionboxel_index(excluding promoted tombstones), so the fan-out reaches every promoted dependent regardless of working-table coverage. Rows present in both tables collapse in the existing(url, type)dedup, and production has the samedepsGIN index the working table uses.fileMetaDocumentFromIndexnow prefers therealm_file_metavalues forcontentHash/contentSize. That table is written in the same critical section as the file's bytes, so it is authoritative for content-derived values; the indexed search-doc/resource values remain as fallbacks for files whose bytes were never hashed at write time.tests/index.tsaccepts aQUNIT_FILTERenv var (trimmed, applied only when non-empty) to narrow a run to individual tests, complementingTEST_MODULES/TEST_FILES..claude/settings.jsonconsolidates the Linear MCP allowlist to one canonical spelling; happy to drop this commit if it should ship separately.Tests
Each guard was verified to fail with its fix reverted, so neither fix is a no-op:
index-writer— invalidations reach dependents whose rows exist only in the production index (transitive dependents reached, promoted tombstones excluded). With the production scan reverted this fails (finds no dependents); guards change Bring in a demo from ember-animated #1.indexing-test— file meta serves write-time contentHash/contentSize over indexed values. With the serving change reverted this fails (serves the stale indexed values); guards change far-match animate Sprite C moving between animation contexts in Basic example #2.Test plan
packages/host: theindex-writerfan-out test passes with the fix and fails with the production scan reverted (verified by rebuilding host and running the test headless).packages/realm-server: theindexing-testserving-precedence test passes with the fix and fails with the serving change reverted (verified locally against a postgres/synapse/prerender stack).🤖 Generated with Claude Code
https://claude.ai/code/session_01UFceD1sbstj9M4K8xRL62R