Skip to content

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

Closed

Description

@dezren39

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) throws TypeError: ... is not iterable
    before the entry filter ever runs.
  • coverage() (live.ts:1073) calls
    (feed?.registry ?? []).filter((r) => r.source === "claude-code")
    — with no isSessionRecord filter at all. A non-array container
    (string, object, number) has no .filter and throws; and an array
    containing a null entry also throws (r.source dereferences
    null), even though registry() 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: isFleetFeed checks only version,
    generatedAt, quota.pools and attention — no registry check.
  • 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 through isSessionRecord (the durable-store load at
    live.ts:632 does Array.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) await source.registry() with no catch;
    attentionContext() (server.ts:273) awaits source.coverage()
    with no catch and backs /attention, /today, the board render
    and /api/coverage.json (:753-755). (Other registry() callers
    at :159/:172/:822 do 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 isFleetFeed can no longer throw in registry()
    or coverage() over its registry section: a non-array container
    (string, object, number) and an array containing null/non-record
    entries both degrade to "no feed records" (the documented
    absent-section behaviour), and /sessions, /api/registry.json,
    /attention, /today and /api/coverage.json render 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 in isFleetFeed vs Array.isArray guards
    at both consumers), consistent with the feed-gate sibling rulings.
  • Regression tests cover: registry as 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

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  4. 35 remaining items

  5. added
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 2, 2026
  6. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked — Drew-owned PR boundary

  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — state: picked-up at=2026-11-02T09:40:00Z (OpenChamber scheduled lane)

  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — state: parked at=2026-11-02T09:42:45Z (OpenChamber scheduled lane)

  9. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 2, 2026
  10. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  11. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/aggregatorPackage/app: aggregator (pm-stack in this repo)app/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions