Skip to content

aggregator(registry): malformed taskRefs elements pass every gate and crash /sessions at render — an array of bad refs merges, persists, then refUrl throws #2485

Closed

Description

@dezren39

Bug: a registry record whose taskRefs is a well-formed array with malformed elements passes every gate, merges, persists — then crashes /sessions at render

#2408 covered a non-array taskRefs crashing mergeRecords' spread. The element-level remainder is still open: when taskRefs IS an array, nobody ever checks what is inside it, and the one consumer that interprets the elements throws on anything that is not exactly owner/repo#<digits>.

The unchecked path:

  • pm-stack/aggregator/scripts/record-claude-launch.ts:44-46 — --task values are copied from argv verbatim into the launch record's taskRefs. The plausible short form --task code#2480 (or a bare --task 2480) writes a malformed ref; nothing validates it.
  • pm-stack/aggregator/src/registry.ts:93-110 — parseLaunchRecords checks only that id is a string; recordFromLaunch (registry.ts:88) copies l.taskRefs ?? [] verbatim, elements included — a string like "code#2480" or a non-string element like 42 passes through unchanged.
  • pm-stack/aggregator/src/registry.ts:138-142 — isSessionRecord, the hygiene check at both load sites (fleet-feed registry at src/sources/live.ts:982, durable store via loadRegistryStore/mergeIntoRegistry at live.ts:610-621), checks only id/source/label strings. An array of malformed elements passes.
  • The merge is fine with them (registry.ts:130 spreads the array), so the poisoned record is written into the durable registry store and survives restarts.
  • pm-stack/aggregator/src/render.ts:1637-1638 — sessionRecordLine maps every taskRef through refUrl, and parseRef (src/model.ts:399-405) throws not a repo-qualified ref: <value> on any element that is not owner/repo#<digits>. renderSessions maps that over every record, and /sessions calls it uncaught (src/server.ts:576-579). One malformed element therefore 500s the whole sessions page — for every record, not just the poisoned one — until the store file is hand-repaired.

Adjacent, same function: parseRef converts the digits with Number(...) unchecked, so a syntactically valid ref with an oversized number (developing-today/code#99999999999999999999999, producible by refsInText from a URL in a session title) parses to 1e+23 and refUrl emits https://github.com/developing-today/code/issues/1e+23 — a silently wrong link instead of a rejection.

Verified this run (main 562663fa)

Sandbox Bun probe importing the real registry.ts, render.ts and model.ts (probe: goal hidden_files/probe-2026-10-29-laneB-taskrefelems/probe.ts):

  • parseLaunchRecords on {"id":"claude-x1","taskRefs":["code#2480"]} and {"id":"claude-x2","taskRefs":[42]} → both records returned with elements verbatim; isSessionRecord returns true for both.
  • mergeRecords(records, records) → succeeds; merged taskRefs [["code#2480"],[42],["developing-today/code#2480"]].
  • refUrl("code#2480") → throws not a repo-qualified ref: code#2480; refUrl(42) → throws the same; control refUrl("developing-today/code#2480") → the correct URL.
  • renderSessions([goodRecord]) → renders OK (18,608 chars); renderSessions(merged) → throws not a repo-qualified ref: code#2480.
  • parseRef("developing-today/code#99999999999999999999999") → {number: 1e+23}; refUrl of it → .../issues/1e+23.

Why it matters

Launch records are appended by hand / by launch scripts (--task is free-form argv), feed registry entries arrive from a collector on another host, and the durable store is a hand-editable file — all three producers are exactly the untrusted-boundary inputs this registry's hygiene check exists for. The failure is maximally disproportionate: one typo'd --task code#123 at some past launch permanently takes down /sessions (the fleet's session-identity page), the poison lives in the durable store so restarts don't help, and the thrown error names the value but not the record carrying it. The huge-number sub-case never crashes at all — it just links a session to a wrong, scientific-notation URL.

Acceptance criteria

  • A registry record whose taskRefs array contains a malformed element (non-string, or a string that is not owner/repo#<digits> with a safe-integer number) can no longer crash renderSessions//sessions: the owning layer is pinned by triage (gate rejects/filters elements in isSessionRecord + recordFromLaunch/parseLaunchRecords, vs the renderer skipping unparseable refs) and applied consistently at the feed, store, and launch-record entry points.
  • A poisoned record already in the durable store does not wedge the page: /sessions keeps serving the remaining records (and ideally the poisoned record renders without its bad refs rather than vanishing).
  • record-claude-launch.ts either validates --task values against the ref shape and refuses/warns at write time, or the issue records why write-time validation is the wrong layer.
  • Regression tests cover at least: launch record with taskRefs: ["code#2480"], launch record with taskRefs: [42], a feed/store record with a malformed-element array rendering /sessions without throwing, and an oversized-number ref not producing a scientific-notation URL.
  • No behaviour change for well-formed records: existing registry/render tests keep passing and good refs render identical links.

Dedup

— filed by new-issue-creator-code-b (Lane B), 2026-10-29 run

Activity

  1. developing-today commented on Oct 30, 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 30, 2026
  3. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator (new-issue-creator-code-b, Lane B)
    • Session / ID: cron new-issue-creator-code-b, 2026-10-29 19:50 CDT run
    • Found via: filed this run
    • Task: Triage the malformed-element taskRefs crash: pin the owning layer (gate/launch-record filtering vs renderer tolerance), route a small_fix packet
    • Expectation: effort/xs triage; done = layer pinned, poisoned-store behaviour decided, regression tests named in the issue run green after the fix
    • Links: probe hidden_files/probe-2026-10-29-laneB-taskrefelems/probe.ts in the keep-the-four-repos-healthy goal workspace (real registry/render/model modules, main 562663f)
  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. 63 remaining items

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

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

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

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up (ownership correction + CI disposition)

  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/fixhas a fix task queued

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions