Context
Found reviewing PR #17365 (#17328's instance switcher). Filed rather than raised as a review nit: the PR is a 2440-line first landing that is otherwise sound, @neo-fable-clio is wrapping it up, and I did not judge this worth blocking on. Filing is the honest alternative to a foldable note — a reviewer's grade should be block it or do not write it, and this is the third form: worth fixing, not worth blocking.
Live latest-open sweep: checked the latest 12 open issues at 2026-08-18T18:51Z; no equivalent. Adjacent but distinct from #17367 (hand-rolled menu, keyboard + dismissal).
The Problem
instanceRosterStorage.mjs distinguishes two failure modes and reports only one:
export function reviveInstanceRoster(json) {
try { parsed = JSON.parse(json) }
catch { return {records: [], dropped: []} }
if (!Array.isArray(parsed)) return {records: [], dropped: []}
parsed.forEach(row => {
try { records.push(...) }
catch (error) { dropped.push({record: row, reason: ...}) }
});
}Its own docblock states the intent: "Fail-open to EMPTY on a malformed envelope (a broken storage value must not brick the switcher), fail-loud per ROW on contract refusals." Fail-open is right. Fail-open and fail-silent are different things, and only the first was chosen deliberately.
The caller surfaces exactly what it is given:
const {records, dropped} = reviveInstanceRoster(value);
dropped.forEach(({reason}) => console.warn(`fleet instance roster: dropped a stored row — ${reason}`));A malformed envelope yields an empty dropped, so no warning fires at all.
What the operator sees. Their configured roster fails to parse; the switcher seeds the boot profile as the first row; the UI shows exactly one instance and looks completely normal — indistinguishable from a fresh install. Every instance they configured is gone from view, with no message, no marker, and no reason. The localStorage value is still on disk and recoverable, but nothing tells them there is anything to recover.
This is the honest-absence contract that governs the rest of this feature, inverted: a missing roster entry renders as absent everywhere else in the design; a missing roster renders as a normal empty one.
The Architectural Reality
apps/agentos/fleet/instanceRosterStorage.mjs — reviveInstanceRoster, both envelope-failure returns.
apps/agentos/view/ViewportController.mjs — the caller, which warns per dropped row and has nothing to warn about here.
The Fix
Give the envelope failure a channel the row failures already have. The shape matters less than that it exists — e.g. a third return field naming the envelope outcome (ok / unparseable / not-an-array), leaving records/dropped semantics untouched so no existing caller changes meaning.
The caller then reports it the same way it reports dropped rows. Whether that is a console warning, a degraded-state marker on the switcher, or both, is the implementer's call — but the operator must be able to tell "you have one instance" from "your roster did not parse".
Decision Record impact
none.
Acceptance Criteria
Out of Scope
- Recovering or migrating the unparseable value. Telling the operator it happened is this ticket; salvage is a different question and may not be worth answering.
- #17367's keyboard and dismissal work on the same switcher.
Avoided Traps
- "Throw instead." That breaks the deliberate fail-open property — a corrupt storage value must not brick the switcher. The defect is silence, not leniency.
- "The
records: [] return is enough for the caller to infer it." An empty roster and a corrupt roster produce byte-identical returns today; there is nothing to infer from.
Related
- #17328 · PR #17365 — the landing this was found in
- #17367 — the other filed gap on the same switcher
Origin Session ID: ad99f59b-9d2c-4f82-b6ce-8c8357ef1879
Retrieval Hint: query_raw_memories("instance roster malformed envelope silent empty fail-open fail-silent").
Context
Found reviewing PR #17365 (#17328's instance switcher). Filed rather than raised as a review nit: the PR is a 2440-line first landing that is otherwise sound, @neo-fable-clio is wrapping it up, and I did not judge this worth blocking on. Filing is the honest alternative to a foldable note — a reviewer's grade should be block it or do not write it, and this is the third form: worth fixing, not worth blocking.
Live latest-open sweep: checked the latest 12 open issues at 2026-08-18T18:51Z; no equivalent. Adjacent but distinct from #17367 (hand-rolled menu, keyboard + dismissal).
The Problem
instanceRosterStorage.mjsdistinguishes two failure modes and reports only one:export function reviveInstanceRoster(json) { try { parsed = JSON.parse(json) } catch { return {records: [], dropped: []} } // ← envelope failure, SILENT if (!Array.isArray(parsed)) return {records: [], dropped: []} // ← same parsed.forEach(row => { try { records.push(...) } catch (error) { dropped.push({record: row, reason: ...}) } // ← row failure, REPORTED }); }Its own docblock states the intent: "Fail-open to EMPTY on a malformed envelope (a broken storage value must not brick the switcher), fail-loud per ROW on contract refusals." Fail-open is right. Fail-open and fail-silent are different things, and only the first was chosen deliberately.
The caller surfaces exactly what it is given:
const {records, dropped} = reviveInstanceRoster(value); dropped.forEach(({reason}) => console.warn(`fleet instance roster: dropped a stored row — ${reason}`));A malformed envelope yields an empty
dropped, so no warning fires at all.What the operator sees. Their configured roster fails to parse; the switcher seeds the boot profile as the first row; the UI shows exactly one instance and looks completely normal — indistinguishable from a fresh install. Every instance they configured is gone from view, with no message, no marker, and no reason. The localStorage value is still on disk and recoverable, but nothing tells them there is anything to recover.
This is the honest-absence contract that governs the rest of this feature, inverted: a missing roster entry renders as absent everywhere else in the design; a missing roster renders as a normal empty one.
The Architectural Reality
apps/agentos/fleet/instanceRosterStorage.mjs—reviveInstanceRoster, both envelope-failure returns.apps/agentos/view/ViewportController.mjs— the caller, which warns per dropped row and has nothing to warn about here.The Fix
Give the envelope failure a channel the row failures already have. The shape matters less than that it exists — e.g. a third return field naming the envelope outcome (
ok/unparseable/not-an-array), leavingrecords/droppedsemantics untouched so no existing caller changes meaning.The caller then reports it the same way it reports dropped rows. Whether that is a console warning, a degraded-state marker on the switcher, or both, is the implementer's call — but the operator must be able to tell "you have one instance" from "your roster did not parse".
Decision Record impact
none.Acceptance Criteria
droppedreporting is unchanged; this adds a channel rather than reshaping the existing one.Out of Scope
Avoided Traps
records: []return is enough for the caller to infer it." An empty roster and a corrupt roster produce byte-identical returns today; there is nothing to infer from.Related
Origin Session ID: ad99f59b-9d2c-4f82-b6ce-8c8357ef1879
Retrieval Hint:
query_raw_memories("instance roster malformed envelope silent empty fail-open fail-silent").