Skip to content

Survive transient fetch failures during file indexing and SystemCard load - #5955

Draft
jurgenwerk wants to merge 4 commits into
mainfrom
file-extract-error-rows-and-system-card-resilience
Draft

Survive transient fetch failures during file indexing and SystemCard load#5955
jurgenwerk wants to merge 4 commits into
mainfrom
file-extract-error-rows-and-system-card-resilience

Conversation

@jurgenwerk

@jurgenwerk jurgenwerk commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

There was a situation in staging where the AI assistant showed 0 skills and single model:

image image

The reason this happened:

  1. The indexer re-indexed a file — skills/index.md, the default room skill. To do that it downloads the file's bytes, and that one download failed with net::ERR_HTTP2_PROTOCOL_ERROR — the HTTP/2 connection between the prerender page and the realm server was reset mid-request — so the markdown parser could not run.
  2. Instead of trying again, the indexer fell back to indexing the file as a generic file, and saved that as a normal success — so nothing ever retried it. The file permanently lost its markdown fields, including kind: skill.
  3. The SystemCard — the card that lists the available LLM models and the default skills — links to that file through a field typed "markdown file". A generic file does not satisfy that type, so loading the SystemCard threw and the whole card became unloadable.
  4. The host caches that failure for the entire session: the model picker drops to the built-in fallback list, and new chat rooms are created with an empty skill list. The empty list is saved into the room's state, so the room stays at 0 skills even after the index is repaired.

Net effect: one reset connection during indexing broke the assistant until someone manually reindexed.

Reindexing solved it for staging — the fresh pass rewrote the bad row, and models and skills came back. (Rooms created during the broken window keep their empty skill list, since room state is persisted; they need the skill re-added or a new session.)

Steps taken to not have this happen again, one commit each:

  1. A failed download now produces an error row instead of a wrong success row. Error rows are retried on the next indexing pass, so the index heals itself. The generic-file fallback stays for its real purpose: content the specific parser cannot handle.
  2. The download retries twice with a short pause before giving up, so most dropped connections never surface at all.
  3. A linked card or file that does not satisfy the field's type is dropped from that field (with a console warning) instead of making the whole linking card unloadable. Direct assignment still validates strictly.
  4. The host retries a failed SystemCard load on a backoff schedule, and applies the default skills when it opens an empty room that never had any — so neither failure is permanent anymore.
  5. A Grafana alert fires on the log line that signals the generic-file fallback, which was previously the only — and unwatched — sign that a file row silently degraded.

The extractor's class-chain fallback exists for parse failures: a subclass
that cannot make sense of the content hands off to its parent so the file
still indexes. A failure to obtain the bytes at all — the fetch rejected,
returned non-ok, or the body stream errored mid-read — was routed through
the same fallback, and the base FileDef 'succeeds' without reading any
bytes. That indexed the file as a bare FileDef with none of its subtype
fields, permanently (a success row is never retried), so one transient
connection reset during indexing silently declassified a markdown skill.

Tag byte-acquisition failures throughout the stream plumbing and abort the
extract with status 'error' when one occurs. The indexer already persists
an error result as a retryable error row, so the next indexing pass repairs
the file instead of the misclassification sticking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files      1 suites   1h 44m 51s ⏱️
4 686 tests 4 672 ✅ 14 💤 0 ❌
4 701 runs  4 687 ✅ 14 💤 0 ❌

Results for commit ce414c5.

Realm Server Test Results

    1 files      1 suites   20m 2s ⏱️
2 403 tests 2 403 ✅ 0 💤 0 ❌
2 486 runs  2 486 ✅ 0 💤 0 ❌

Results for commit ce414c5.

@jurgenwerk
jurgenwerk force-pushed the file-extract-error-rows-and-system-card-resilience branch 2 times, most recently from d4044ad to df8b3cd Compare September 2, 2026 09:22
jurgenwerk and others added 3 commits September 2, 2026 11:43
A link field's target lives in another document, and its serialization can
be wrong independently of the card that links to it — a misclassified index
row serves the file with the wrong adoptsFrom, so it rehydrates as a type
the field does not accept. Failing validation there made the whole linking
document unloadable: one bad file row in a public realm broke every card
that referenced it, including the SystemCard that supplies the assistant's
model list and default skills.

During deserialization, drop a linksTo target (to null) or a linksToMany
entry that does not satisfy the field's declared type, with a console
warning. Direct assignment does not pass through the new guard, so a
user-set mismatch still throws.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two self-healing gaps around the SystemCard, both visible when one load
fails at boot:

- matrix-service cached the failure for the whole session, so the assistant
  stayed on the fallback model list and hardcoded skills even after the
  card became loadable seconds later. Retry the load on a bounded backoff
  schedule, mirroring retryUnreachableRealmServersTask.

- A room created while the default-skill lookup was failing keeps an empty
  skills state forever: room state is persisted, the panel reuses the
  unused room as the 'new session', and nothing re-applies the defaults —
  the user cannot escape the 0-skill room. On entering a message-less room
  that never had a skill attached (enabled or disabled), apply the default
  skills.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'extractor fallback while indexing file' log line was the only signal
that a file indexed as a coarser type than its extension warrants, and
nothing watched it — a degraded row went unnoticed until a user-visible
feature depending on the file's subtype fields broke. Add a Grafana alert
rule on that line in the worker log group, mirroring the structure of the
worker-status-group rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jurgenwerk
jurgenwerk force-pushed the file-extract-error-rows-and-system-card-resilience branch from df8b3cd to ce414c5 Compare September 2, 2026 09:44
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.

1 participant