Skip to content

Fix indexed values going stale: module-edit fan-out misses and one-generation-old file content meta#5595

Open
habdelra wants to merge 8 commits into
mainfrom
claude/cs-12172-evergreen-comments-khnwpn
Open

Fix indexed values going stale: module-edit fan-out misses and one-generation-old file content meta#5595
habdelra wants to merge 8 commits into
mainfrom
claude/cs-12172-evergreen-comments-khnwpn

Conversation

@habdelra

@habdelra habdelra commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Indexed values could go stale in two ways that no reindex would heal:

  1. A module edit never re-rendered some dependent instances. The invalidation fan-out (itemsThatReference) scanned only boxel_index_working and prerendered_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 (null for a new field) until each instance file was touched individually.

  2. Rewriting a file's bytes left linked cards with the previous contentSize/contentHash. When a consumer card is rendered, fileMetaDocumentFromIndex served 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

  • itemsThatReference now also scans production boxel_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 same deps GIN index the working table uses.
  • fileMetaDocumentFromIndex now prefers the realm_file_meta values for contentHash/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.ts accepts a QUNIT_FILTER env var (trimmed, applied only when non-empty) to narrow a run to individual tests, complementing TEST_MODULES/TEST_FILES.
  • (Separable) .claude/settings.json consolidates 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:

Test plan

  • packages/host: the index-writer fan-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: the indexing-test serving-precedence test passes with the fix and fails with the serving change reverted (verified locally against a postgres/synapse/prerender stack).
  • eslint clean on the touched files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UFceD1sbstj9M4K8xRL62R

claude added 4 commits July 23, 2026 18:51
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
@habdelra
habdelra requested a review from Copilot July 23, 2026 20:58
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files  ±0      1 suites  ±0   3h 1m 53s ⏱️ +19s
3 630 tests +3  3 615 ✅ +3  15 💤 ±0  0 ❌ ±0 
3 649 runs  +3  3 634 ✅ +3  15 💤 ±0  0 ❌ ±0 

Results for commit 3ecc57d. ± Comparison against earlier commit ec16ba0.

Realm Server Test Results

    1 files  ±    0      1 suites  ±0   13m 33s ⏱️ +13s
1 935 tests  -     1  1 935 ✅ ±    0  0 💤 ±0  0 ❌  - 1 
2 014 runs   - 2 016  2 014 ✅  - 2 014  0 💤 ±0  0 ❌  - 2 

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 itemsThatReference to scan promoted boxel_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/contentSize preferentially from realm_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.

Comment thread packages/realm-server/tests/index.ts Outdated
Comment thread .claude/settings.json
claude added 3 commits July 23, 2026 21:09
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
@habdelra
habdelra requested a review from a team July 24, 2026 00:22
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.

3 participants