Skip to content

aggregator(opencode): mapServerSessions never validates the v2 wire containers — a non-array data on any endpoint, or one null session entry, throws out of the mapper and drops every session from that server; an id-less entry fabricates an undefined-sessionId row #2543

Closed

Description

@dezren39

aggregator(opencode): mapServerSessions never validates the v2 wire containers — a non-array data on any endpoint, or one null session entry, throws out of the mapper and drops every session from that server; an id-less entry fabricates an undefined-sessionId row

Summary

pm-stack/aggregator/src/sources/opencode.ts maps an OpenCode v2 server's responses into feed entries with no validation of the wire containers or the session entries themselves. mapServerSessions (opencode.ts:116) trusts that every endpoint's data is an array and that every session entry is an object with an id:

  • The session list is used unguarded: const sessions = list.data (:125), the token loop dereferences s.tokens on every entry (:145-148), and the inclusion filter/sort dereference s.time / s.id (:150-157).
  • The permission list is iterated unguarded (:139, for (const p of permResp?.data ?? [])).
  • In mapOne, the message list is iterated unguarded (:225, for (const m of data) where data = msgs?.data ?? [], :213).

Any of these shapes therefore throws a TypeError out of mapServerSessions instead of returning entries or null. Both callers are deliberately fail-soft per server — the collector wraps the call in one catch and logs "sandbox server read failed; attention empty" (scripts/collect-fleet-feed.ts:464-502), and live.ts catches around laptopSessions() / laptopMapped() and marks the laptop part failed (live.ts:907-918, :1012-1019) — so the throw drops every session from that server for the run/read (attention, registry records, and pool-burn attribution), even when all but one element were well-formed. Separately, a session entry with no id passes the inclusion filter when recently updated and is mapped into an entry whose sessionId is undefined, which then keys the live byId map and registry records under undefined.

The v2 wire shapes are a moving external contract (the file header notes they were "probed live 2026-10-25" against two servers, and the two servers run different builds — the laptop's managed server and the sandbox's), so shape drift on one endpoint or one corrupt entry is exactly the failure this mapper must degrade through, the way getJson already degrades a failed fetch to null.

Evidence (verified this run, main 7d1b081)

Probe: laptop Bun 1.4.2, the real src/sources/opencode.ts (verbatim copy, md5 28215b8ddcefd61d13188b5d4663f4bf) driven against a stub v2 server (~/git/fleet/probe-2026-10-29-laneB-ocmap/probe.ts on the laptop; copy in the goal's hidden_files):

  • Control (well-formed shapes): RETURNED entries=1 ids=ses_good.
  • Session-list data is an object ({ ses_good: {...} }): THREW TypeError: {} is not iterable — zero entries.
  • Session list [good, null]: THREW TypeError: null is not an object (evaluating 's.tokens') — the good session is lost too.
  • Permission data is an object: THREW TypeError: {} is not iterable — zero entries.
  • Message data is an object for the one included session: THREW TypeError: {} is not iterable — zero entries.
  • Session list [good, { title, time.updated }] (no id): RETURNED entries=2 ids=ses_good, — a second entry with an empty/undefined sessionId is fabricated.
  • Benign controls: a string entry in the session list is excluded by the inclusion filter (no crash); active data as an array still works via Object.keys.

File:line (main 7d1b081):

  • pm-stack/aggregator/src/sources/opencode.ts:122-125 — list fetch; if (!list?.data) return null; const sessions = list.data; — truthiness is the only check.
  • pm-stack/aggregator/src/sources/opencode.ts:137-142 — permission iteration unguarded.
  • pm-stack/aggregator/src/sources/opencode.ts:145-157 — token loop and inclusion filter dereference every entry.
  • pm-stack/aggregator/src/sources/opencode.ts:206-225 — mapOne message iteration unguarded (forms at :199-200 index [0] only, so an object data there is benign).
  • pm-stack/aggregator/scripts/collect-fleet-feed.ts:464-502 — collector catch: one throw = sandbox attention empty, no registry records, no burn attribution.
  • pm-stack/aggregator/src/sources/live.ts:907-918,1012-1019 — live catches: one throw = laptop sessions/registry contribution silently absent.

Why it matters

This mapper is the only path for live OpenCode sessions (both servers) into the attention feed, the durable session registry, and per-pool burn attribution. Its contract with callers is "null or entries, fail soft per server" — but a single malformed element or one endpoint's shape drift currently has the same blast radius as the whole server being down, with a log line that says "read failed" rather than naming the shape problem. As the v2 API evolves independently on the two servers, this is the most likely way the fleet silently loses sight of running sessions.

Acceptance criteria

  • Triage pins the degradation shape (expected: each endpoint's data is array-checked — a non-array degrades that endpoint's contribution the way a failed fetch already does; session entries that are not objects or carry no string id are skipped per entry so valid siblings still map; mapOne's message/form reads degrade per session instead of rejecting the whole server's map — or triage pins a different shape; throwing out of mapServerSessions for a parseable-but-malformed response is the one ruled-out option).
  • Regression tests in the aggregator suite (opencode.test.ts's stub-server home): list data as an object, a [valid, null] list, permission data as an object, and message data as an object each still return the valid session's entry; an id-less entry produces no entry.
  • No behavior change for well-formed wire shapes: the existing opencode.test.ts expectations (inclusion window, signals, source stamp, unreachable-server null) pass unchanged.

Filed by new-issue-creator-code-b (lane B), run 2026-10-29 22:20 CDT. Dedup: #2286 is iso() RangeError on out-of-range timestamps in otherwise-valid entries (different function/failure — a huge numeric timestamp, not container/entry shape); #2531 is the Jules keeper-state mapper (different source/file); the isFleetFeed gate family (#2439/#2445/#2446/#2450/#2451/#2539) covers feed-file sections in live.ts, not this live-server mapper. gh searches for mapServerSessions / "not iterable" returned no covering issue.

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

    • Agent: new-issue-creator job (new-issue-creator-code-b, lane B)
    • Session / ID: cron new-issue-creator-code-b, run 2026-10-29 22:20 CDT
    • Found via: filed this run
    • Task: Triage the mapServerSessions wire-validation gap: pin the per-endpoint / per-entry degradation shape, then route a small_fix packet
    • Expectation: effort/xs triage; done = degradation shape pinned and fix packet routed
    • Links: probe ~/git/fleet/probe-2026-10-29-laneB-ocmap/ (laptop); evidence in issue body
  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. 36 remaining items

  5. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — holder: parked by repo-worker-code at 2026-11-02T06:57:08Z, reason: Drew-owned canonical PR #4212 remains open. No PR content, diff, branch, worktree, CI, or review was inspected beyond metadata; nothing was changed or merged. Resume after explicit ownership release/handoff or PR terminal state, then revalidate.

  6. 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
  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

    • Agent: queue worker (repo-worker-code), lane C shift
    • Session / ID: task 5372
    • Found via: queue claim (resumed from parked)
    • Task: Land PR fix(aggregator): validate OpenCode v2 session wire shapes (#2543) #4212 (validate OpenCode v2 session wire shapes).
    • Expectation: Ownership re-determined this shift: the earlier Drew-owned park was a metadata-only heuristic. Fresh evidence says fleet work — the fleet worktree ~/git/fleet/5372 sits on branch queue/5372-2543-opencode-wire-validation at the PR head, and the PR body is the creating workers evidence record. Re-verified on head 48f19ae: worktree clean, bun test test/opencode.test.ts 6/6 pass on the laptop, diff is exactly the two intended files, Codex review completed with no findings, PR MERGEABLE. The PR-level test check (run 41396717869) is still queued in the fleet runner backlog; merging under the standing merge authority (verified change; CI infra backlog is not a failure), with main CI as the post-merge gate.
    • Links: PR fix(aggregator): validate OpenCode v2 session wire shapes (#2543) #4212, task 5372
  8. added a commit that references this issue on Nov 2, 2026
  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Fixed by PR #4212, merged as 9a3c52d (task 5372, lane C): mapServerSessions now rejects malformed session-list envelopes, skips rows without a nonempty string id, and degrades malformed optional endpoint contributions while preserving valid sibling sessions. Verified on head 48f19ae: bun test test/opencode.test.ts 6/6 pass (laptop), targeted tsc pass, Codex review no findings.

  10. 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