Skip to content

aggregator(feed): non-array projects crashes items()/projects() — the .filter(isProjectPoolItem) call itself throws, taking down board/today/roadmap #2453

Closed

Description

@dezren39

Problem

The live source's two Project-pool consumers assume the fleet feed's
projects section is an array without ever checking:
(feed?.projects ?? []).filter(isProjectPoolItem)
(pm-stack/aggregator/src/sources/live.ts:703 in items() and
:742 in projects()). isFleetFeed
(pm-stack/aggregator/src/feed.ts:207-219) never checks projects
at all, so a feed whose projects is a string, object or number
passes the gate — and .filter does not exist on those values, so
both consumers throw a TypeError instead of serving an empty pool.
The entry-level filter works fine (a [null] pool is filtered to
empty); it is the container shape that is unguarded.

Evidence (verified this run, main 5a34fa70)

  • feed.ts:61-66: FleetFeed.projects?: ProjectPoolItem[] — absent
    when the collector had no Projects token or the read failed.
  • feed.ts:207-219: isFleetFeed contains no projects check.
  • sources/live.ts:703: items() computes the pool with
    (feed?.projects ?? []).filter(isProjectPoolItem) before any
    other work; :740-743: projects() does the same.
  • Most projects() call sites are defended
    (server.ts:267,558,574,586,609,620,745 use
    .catch(() => []), and the provenance primer at :95-112 uses
    Promise.allSettled) — but items() is not: boardContext()
    (server.ts:222-227) and roadmapContext() (:229-234) await
    source.items(refs) with no catch, and they back the board page
    (boardRender, :263-265), /today (todayContext, :306-307),
    /roadmap (:479), /api/board.json (:644) and
    /api/roadmap.json (:648). A throw in items() 500s all of
    them — the aggregator's main board surfaces, not just a pool facet.

Laptop Bun probe (~/git/fleet/probe-2026-10-29-laneA-feedpayg/
probe3.ts, the REAL liveSource built exactly as
test/projects.test.ts:158 builds it — real config.ts +
sources/live.ts from main 5a34fa70, md5-identical feed.ts,
Bun 1.4.2; feed files written to a temp dir):

projects in feed src.projects() src.items([])
valid pool (control) OK — 1 served OK
[null] (entry-level) OK — 0 served (filtered) OK
"oops" (non-array string) throws TypeError: (feed?.projects ?? []).filter is not a function throws same
{...} (non-array object) throws same throws same
42 (number) throws same throws same

Why it matters

The collector writes projects from a GraphQL read that can fail or
drift independently of the rest of the feed; the section is documented
as absent on failure, but nothing enforces array-ness when it is
present-but-wrong. One bad container value takes down the board,
today and roadmap pages plus their JSON twins — a much larger blast
radius than the pool facet itself, and a different failure mode from
the entry-level gaps: here even the defensive .filter(...) call is
the crash site. Related but distinct: #2418 (entry-level —
isProjectPoolItem never checks labels inside an entry that
passes the filter) and the feed-gate siblings #2427/#2429/#2439/
#2445/#2446/#2450/#2451 (isFleetFeed never checks those sections at
all; their consumers dereference entries rather than calling
.filter on the container).

Acceptance criteria

Dedup

gh searches (open + closed) for projects filter aggregator,
payg, isFleetFeed and isFleetFeed never checks returned no
covering issue. #2418 is the entry-level labels gap inside
isProjectPoolItem — the filter this issue's consumers call — not
the container shape those consumers assume.

Activity

  1. developing-today commented on Oct 29, 2026

    @developing-today
    Contributor
  2. 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 29, 2026
  3. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  4. 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
  5. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Triage findings — task 3669

    Already fixed on current main 2841b8de0; no follow-up fix task is needed.

    • pm-stack/aggregator/src/feed.ts:259-264 — shared poolFromFeed returns [] for absent or non-array projects, and filters array entries.
    • pm-stack/aggregator/src/sources/live.ts:742-751 (items()) and :795-801 (projects()) both use this shared helper, so the optional feed section degrades to an empty pool without rejecting the rest of the feed.
    • The helper and both call sites landed in merged PR pm-stack: Project pool graph edges contract (#2399, part 1 of #1122) #2744 (111a31806, part of impl(aggregator+collector) part 1 of 2: Project pool graph edges contract (split of #1122) #2399); that merge is an ancestor of current main. This pins the layer at the shared feed-pool reader, not whole-feed rejection in isFleetFeed or duplicated guards in each consumer.
    • Verified with bun test test/feed.test.ts test/projects.test.ts (34 passed, 0 failed) and a real liveSource probe: projects as string, object, or number returns projects() = [] and items() resolves.

    This issue was filed before #2744 landed; closing as already implemented.

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)effort/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 jobstatus:donetask/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