[Bug] One unmigratable v0 artifact (subagent/descriptor v2) disables all content search #6328
christianschmidt
started this conversation in
General
Replies: 1 comment
|
Follow-up with an empirical result. We converted the version stamp on all affected artifacts (81 of them) and put them back: the only byte changed in each file is the descriptor version, and DSH's own v0 to v1 chain then read every one of them without complaint. All 81 are now in the full-text index, together with the rest of the store, and search works. So the payload really is v3-compatible in practice, not just by the validator rules, and the conversion in suggestion 1 would recover such artifacts losslessly. The containment gap in suggestion 2 is still the part that matters going forward, since a future inventory-tightening can produce the same class again. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Disclosure
This report was written by a DSH agent session with the operator.
Environment
@deepseek-ai/dsh0.1.5-rc.2,c291e7961a(2026-09-10)webwithsession-query-sqliteconfigured asopenAt: first-searchplus a durablepath(the shipped default isopenAt: never, which keeps this failure dormant).~/.dsh/sessions.Summary
Enabling content search (
session-query-sqlite,openAt: first-search) fails for the whole session store: every search returnsSESSION_QUERY_PERSISTENCE_FAILEDand the derived index stays empty. A single format-generation-0 artifact containing asubagent/descriptorevent withversion: 2is enough to cause this. The frozen v0 to v1 migration refuses that combination, and the search observation turns one unreadable entry into a failure of the entire corpus.Two findings, separable:
subagent/descriptorwith version 2 inside a v0 artifact is DSH's own output: the writer stamped version 2 until 2026-08-24, and the migration that refuses it was added on 2026-09-01. The payload satisfies every version 3 validation rule and differs only in the version stamp.The migration policy itself is not what this report challenges; the section below lists what we understand to be intentional. What we are asking for is (a) a decision or documentation for this specific combination and (b) per-artifact containment plus a cause that reaches the user.
No existing report
We searched the discussion archive for these error strings and behaviours (
SESSION_QUERY_PERSISTENCE_FAILED,unsupported descriptor version,session-search persistence observation failed,content search,session-query,openAt,format migration,descriptor version) and found no duplicate. The closest threads, #496 and #501, describe unrelated causes of unreadable session history.What we understand is by design, and are not asking to change
.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md:17). Migration is therefore lazy and write-triggered, and there is no upgrade-time pass over the store.We also did not modify any artifact; see Workaround.
Environment
c291e7961a(2026-09-10),@deepseek-ai/dsh0.1.5-rc.2web;session-query-sqliteconfigured withopenAt: first-searchand a durablepath. The shipped default isopenAt: never, which keeps this failure dormant and is a plausible reason it went unnoticed.~/.dsh/sessionsReproduction
Have one format-generation-0 artifact (
session.jsonl.zstd, header"version":0) containing asubagent/descriptorevent withdata.version: 2. Minimal shape:{"type":"session","version":0,"id":"<id>","createdAt":0,"cwd":"/tmp"} {"type":"subagent/descriptor","seq":0,"time":0,"data":{"version":2,"mode":"continuable","provider":"spawn","label":"x","agentProvider":"p","agentModel":"m"}}The descriptor is not necessarily among the first events. In our store it sits at
seq 0in some artifacts and atseq 5in others, so a scan that only inspects the first lines finds a subset. See the appendix.Trigger a whole-corpus observation: content search in the Web sidebar, or
ctx.sessionQuery.searchSessions({ query: 'x' }).The search fails, and the derived index database is created but stays empty.
Observed
The reconcile commits everything in a single transaction, so the failure lands before the first commit:
Retrying cannot make progress. The Web sidebar degrades to "Content search is temporarily unavailable. Showing name matches.", which is title and workspace matching only.
Root cause
A1. The frozen chain has no rule for this combination
packages/session/session-format-v0-to-v1/src/validation.ts:198-202:versionis the source artifact's format generation. For a v0 artifact the descriptor must therefore already be version 3, so a v0 artifact written by a build that stamped version 2 is permanently unreadable: neither converted nor degradable. For source generations 1 and above the same condition is explicitly tolerated viareturn, which means this strictness is specific to the v0 chain.A2. The combination is DSH's own output, written before the chain existed
SUBAGENT_DESCRIPTOR_VERSIONwas2until commitf76a225a7d(2026-08-24, PR #2663 "feat/subagent-provider") raised it to3:The v0 to v1 chain itself was added later, in
d1521ea783(2026-09-01, "feat(session)!: add released format migration"). Artifacts written before both changes were valid when written and are refused today. In our store the affected artifacts were all written between 2026-08-23 and 2026-08-26, that is, inside exactly that window.This is not a one-off. The format generation stayed at
0while the descriptor payload version changed from 2 to 3, so v0 artifacts exist with both descriptor versions. A payload shape changed under a stable format generation, and the frozen inventory pinned only the newer shape. The same pattern can produce the same class of unreadable artifact again.A3. The payloads are field-compatible, so the gap looks convertible
subagentDescriptorValue()(packages/session/session-format-v0-to-v1/src/payload-validation.ts:955-979) requires, formode: "continuable":version === 3, a non-emptyprovider,mode === "continuable", a non-emptylabel, and optionalagentProvider/agentModel(which must be paired). The disposition table (dispositions.ts:92) permits exactly:Every affected payload we inspected is shape-identical and satisfies all of that with no unknown key:
{"version":2,"mode":"continuable","provider":"spawn","label":"...","agentProvider":"p","agentModel":"..."}The only difference from an accepted artifact is the value of
version. That is the basis for the question in Suggested fixes, not a claim that the refusal is wrong as such.B1. One artifact fails the whole observation
SqliteSessionQueryEngine._observeStable()(packages/session-query/session-query-sqlite/src/index.ts:495-562) reads the cold logs of all non-live persisted sessions inside onetryblock. Any throw, including the typed migration refusal for a single artifact, is converted into a store-level error:There is no per-entry containment. The loop aborts at the first unreadable artifact, and because the write phase never starts, the derived index stays empty. Both
searchSessionsandsearchEventsuse this path.B2. The specific cause is flattened before the user sees anything
SessionFormatUnsupportedMigrationError(packages/session/session-format/src/error.ts:7), a precise and typed migration error.SessionQueryErrorcarrying the store-level codeSESSION_QUERY_PERSISTENCE_FAILED, with the message preserved and the cause attached.RemoteError('gateway/internal', 'session search failed: ...')(packages/api/session-controller/src/list.ts:259-265).packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx:815); the artifact path that the message does carry never reaches the UI.Note that
SESSION_QUERY_PERSISTENCE_FAILEDis also the code used for genuine store-level conditions such as "persistence observation did not stabilize after one retry", so a single-artifact migration refusal and a store-wide outage are indistinguishable to callers.B3. Per-artifact containment already exists elsewhere in the product
The subagent control listing defines containment semantics for exactly this kind of data damage (
packages/subagent/subagent/src/control-types.ts:66-77): a candidate whose log makes a registered unit's fold throw is reported asreason: 'corrupt', covering "a missing, malformed, or unrecognized-version descriptor, deliberately undistinguished", and that damage is "contained per child". Theunavailablemember covers an absent or transiently unreadable observation, and theunsupportedmember is retained in the union but never produced.Relatedly, the subagent layer's own fold treats an unknown descriptor version as merely unclassifiable rather than fatal:
parseSubagentDescriptor()returnsundefinedwhenversion !== SUBAGENT_DESCRIPTOR_VERSION(packages/subagent/subagent/src/descriptor.ts:210, contract at:306-314, "the child cannot be classified by this runtime"), and the identity projection projectsnullwith "the causes deliberately undistinguished" (projection.ts:165-180).So the principle that an artifact we cannot interpret yields no row rather than a crash does exist in the codebase. We are not aware of an equivalent on the corpus observation path.
Impact
ctx.sessionQueryoperations are not affected by the corpus observation: listing,filterSessionsand lineage traces work from headers. What fails is any body read of that artifact, because the query service's observation reader uses the same cold read (packages/session-query/session-query/src/observation.ts:14,188-195). We verified that path in code but did not separately reproduce the per-session UI symptom.Workaround
Quarantine the affected session directories out of the persistence root: move them from
~/.dsh/sessionsto a directory outside the store, keeping the directory structure. After that the corpus observation succeeds and search works. We deliberately did not rewrite any artifact, and the move is fully reversible.Suggested fixes
subagent/descriptorwithversion: 2by validating the payload with the version 3 rules and stampingversion: 3, or by adding an explicit and documented v2 to v3 step. The payload evidence in A3 suggests the conversion is lossless. If the refusal is intentional, a sentence in the migration documentation or the release notes stating that such artifacts are permanently unreadable, together with a concrete remedy, would remove the silent-outage aspect.raw log:location, reach the host error and the UI instead of a generic "Content search is temporarily unavailable", and consider a code that distinguishes "one artifact is unreadable" from "the store is unreachable".Appendix: how to find affected artifacts
The descriptor is not necessarily among the first events, so check every line rather than a head window:
All artifacts this finds are unmigratable by the current build. In our store they belong to subagent sessions only, which is consistent with the descriptor being the trigger.
Closing note. This is early-stage software, and the artifacts involved are subagent logs from a pre-release period, so this may well be an acceptable edge case, and losing those particular sessions is not something we consider critical. We still think it is worth reporting, for reasons that go beyond this one store. Three of them: the error that reaches the user names no artifact, so the cause is not traceable from the UI; one unreadable entry disables a whole feature instead of degrading, which turns a local data problem into a global outage; and the underlying pattern is not limited to very old versions or early adopters, because a payload shape changed here while the format generation stayed the same. Any migration that freezes its inventory after such a change can produce the same class of artifact again, so containing it per artifact seems more valuable than converting this single case. Happy to re-test either change on a build that carries it.
All reactions