Repository navigation
Description
Problem
The fleet feed's registry section is never checked by isFleetFeed
(pm-stack/aggregator/src/feed.ts:207-219), and its two live-source
consumers handle it in two different unguarded ways:
registry()(pm-stack/aggregator/src/sources/live.ts:1009-1010)
iterates it directly:for (const r of feed.registry) if (isSessionRecord(r)) incoming.push(r). A non-iterable container
(plain object, number) throwsTypeError: ... is not iterable
before the entry filter ever runs.coverage()(live.ts:1073) calls
(feed?.registry ?? []).filter((r) => r.source === "claude-code")
— with noisSessionRecordfilter at all. A non-array container
(string, object, number) has no.filterand throws; and an array
containing anullentry also throws (r.sourcedereferences
null), even thoughregistry()filters that same entry out fine.
Evidence (verified this run, main 7d1b081c)
feed.ts:38-39:FleetFeed.registry?: SessionRecord[]— identity
records (impl(dashboard): slice 3 — pluggable pages (DNS register first), comments everywhere, agent/session identity registry #906 §3) from sandbox OpenCode + Jules + Muse launches.feed.ts:207-219:isFleetFeedchecks onlyversion,
generatedAt,quota.poolsandattention— noregistrycheck.live.ts:1009-1010:registry()for-of over the raw container.live.ts:1073:coverage()unfiltered.filter((r) => r.source === "claude-code")— the only registry consumer that does not pass
entries throughisSessionRecord(the durable-store load at
live.ts:632doesArray.isArray(parsed) ? parsed.filter( isSessionRecord) : [], so the store path is safe; the feed path
is not).- Uncaught call sites:
server.ts:604(/sessions) and:613
(/api/registry.json) awaitsource.registry()with no catch;
attentionContext()(server.ts:273) awaitssource.coverage()
with no catch and backs/attention,/today, the board render
and/api/coverage.json(:753-755). (Otherregistry()callers
at:159/:172/:822do catch.)
Laptop Bun probe (~/git/fleet/probe-2026-10-29-laneA-feedregistry/,
the REAL liveSource built via loadConfig exactly as
test/orchestration.test.ts builds it; feed.ts, sources/live.ts
and config.ts md5-identical to main 7d1b081c, Bun 1.4.2; feeds
written to a temp dir, registry store pointed at the temp dir —
entry counts include the laptop server's live sessions, which
registry() merges in by design):
registry in feed |
isFleetFeed |
src.registry() |
src.coverage() |
|---|---|---|---|
| absent (control) | true | OK | OK |
| valid array (1 record) | true | OK — record merged | OK |
[null] (entry-level) |
true | OK — filtered by isSessionRecord |
throws TypeError: null is not an object (evaluating 'r.source') |
"abc" (string) |
true | OK — chars filtered out | throws TypeError: (feed?.registry ?? []).filter is not a function |
{...} (object) |
true | throws TypeError: {} is not iterable |
throws .filter is not a function |
42 (number) |
true | throws TypeError: number is not iterable |
throws .filter is not a function |
Why it matters
The collector assembles the registry from three independent launch
sources; the section is documented as the identity record of record
for /sessions, author identities and cross-repo session links.
One drifted container value 500s /sessions and
/api/registry.json; one null entry — which the sibling consumer
handles correctly — 500s every surface backed by coverage()
(attention, today, board, coverage JSON). The two consumers
disagreeing about the same section's hygiene is the bug: the feed
path needs the same array + isSessionRecord discipline the
durable-store path already has (live.ts:632).
Related but distinct: #2408/#2376/#2485 are field-level gaps
inside records that pass isSessionRecord (taskRefs, lastSeenAt);
#2453 is the projects container in items()/projects(). This
one is the registry container plus coverage()'s missing entry
filter — no open or recently closed issue covers it (checked this
run: title/keyword searches for the feed registry gate return only
the field-level registry issues above and the other feed siblings
#2427/#2429/#2439/#2445/#2446/#2450/#2451).
Acceptance criteria
- A feed passing
isFleetFeedcan no longer throw inregistry()
orcoverage()over itsregistrysection: a non-array container
(string, object, number) and an array containingnull/non-record
entries both degrade to "no feed records" (the documented
absent-section behaviour), and/sessions,/api/registry.json,
/attention,/todayand/api/coverage.jsonrender normally. coverage()'s Claude-record count uses the same entry hygiene as
every other registry consumer (isSessionRecord, or the gate
guarantees it) — pinned by triage together with the layer choice
(gate-level array check inisFleetFeedvsArray.isArrayguards
at both consumers), consistent with the feed-gate sibling rulings.- Regression tests cover:
registryas a string, as an object, as a
number, and as[null]—registry()resolves,coverage()
resolves, and a well-formed registry still merges and counts its
records unchanged.
Activity
dezren39 commentedon Oct 30, 2026
🤖 Agent note · filed
35 remaining items
dezren39 commentedon Nov 2, 2026
🤖 Agent note · parked — Drew-owned PR boundary
dezren39 commentedon Nov 2, 2026
agent-note:v1 — state: picked-up at=2026-11-02T09:40:00Z (OpenChamber scheduled lane)
dezren39 commentedon Nov 2, 2026
agent-note:v1 — state: parked at=2026-11-02T09:42:45Z (OpenChamber scheduled lane)
dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up
awaiting_cicheckpoint: canonical PR fix(aggregator): sanitize optional registry feed #4196 (branchqueue/5361-2539-registry-sanitize) already implements the loadFeed registry sanitization. The earlier ownership park was the shared-account false positive — PR fix(aggregator): sanitize optional registry feed #4196 is a fleet PR bound to task 5361 by branch name and body, not Drew-owned work. Revalidating PR fix(aggregator): sanitize optional registry feed #4196 checks and Codex findings fresh, then merging when verified.queue/5361-2539-registry-sanitizedezren39 commentedon Nov 2, 2026
🤖 Agent note · done
d7b6e8a0d). The loadFeed registry sanitization for issue aggregator(feed): isFleetFeed never checks registry — a non-array container crashes registry()/coverage(), and coverage()'s unfiltered .filter throws on a [null] entry that registry() handles fine #2539 is on main: absent registry stays absent, non-array sections become an empty registry, malformed rows drop individually viaisSessionRecord, andregistryDroppedrecords drops —registry()andcoverage()now consume the same safe rows.bun test test/feed.test.ts test/orchestration.test.ts test/registry-hygiene-2408.test.tson the merged tree: 86 pass, 0 fail. Codex review clean on both heads (ae2c8698f,90312318c: no findings). The earlier CItestfailure on PR fix(aggregator): sanitize optional registry feed #4196 was only the two pre-existing main failures (slice-3 dns/sessions/pages; hub service detail), verified against clean main by the previous shift.queue/5361-2539-registry-sanitize