Skip to content

projector(comments): pmp_comments is keyed by the bare GitHub numeric ID — an issue comment, a review comment and a review with the same ID silently overwrite each other #2694

Open

Description

@dezren39

Problem

pmp_comments is keyed by the bare GitHub numeric ID alone, but the projector writes three different kinds of GitHub objects into that one table — issue comments, PR review comments, and PR reviews — and GitHub assigns those IDs from independent sequences per object type. The same number can therefore denote an issue comment and a review on the same (or different) items, and when it does, the second object silently replaces the first (main b3f6d2b):

  • pm-stack/projector/schema.sql:47 — id TEXT PRIMARY KEY, -- GitHub comment/review id, no kind or repo qualifier.
  • pm-stack/projector/src/store.ts:207 — upsertComment is ON CONFLICT(id) DO UPDATE SET over every column including ref, kind, body and deleted (guarded only by source_updated_at >=, which a later-created colliding object passes).
  • Every writer passes the raw per-type ID: webhook commentCommon (src/projector.ts:80, String(c.id)), the delete paths (src/projector.ts:159 and :213, markCommentDeleted(String(comment.id)) — a delete for one kind can tombstone another kind's row), and reconciliation (src/reconcile.ts:76 issue/review comments, :116 reviews, both String(...id)).

The unified activity stream (schema view pmp_v_subject_activity, getCommentsForSubject) then shows the surviving object and has lost the other one — no error anywhere, and reconciliation re-derives the same collision on every poll.

Evidence (verified this run)

Probe on the laptop (~/git/fleet/probe-2026-10-30-laneB-projector/probe.ts, real store.upsertComment/getComments against an in-memory libSQL DB migrated from the real schema.sql, main b3f6d2b): upsert an issue_comment with id 424242 (created 2026-01-01), then upsert a review with the same id 424242 (created 2026-01-02):

collision: rows=1 kinds=review bodies=review body
pmp_comments total rows = 1

The issue-comment row is gone — overwritten in place by the review, including its ref and kind. (The fixtures hint the namespaces were kept apart by hand: fixtures/03-issue-comment.json uses id 9001, 05-pr-review.json 10001, 06-pr-review-comment.json 11001.)

Why it matters

The projector's whole purpose is a trustworthy unified comment/activity projection across the four repos (aggregator requirement #2). A cross-kind ID collision silently drops a real comment or review from every subject view and can cross-wire a deletion onto the wrong object. Because the monotonic guard compares source_updated_at, the corruption sticks: re-delivering the overwritten object's webhook is a no-op duplicate or loses the guard comparison, so the lost row does not heal itself.

Acceptance criteria

  • Comment identity distinguishes the object kind (e.g. a composite key or a kind-qualified ID) at the schema level and in upsertComment / markCommentDeleted, so an issue comment, a review comment, and a review with the same numeric ID coexist as three rows.
  • A delete event for one kind never tombstones a row of another kind.
  • Store/projector tests pin the same-ID-different-kind case for all three kinds, plus the cross-kind delete case.
  • Existing rows and the read model (getComments, getCommentsForSubject, pmp_v_subject_activity) keep working; the migration path for an existing database is stated in the PR (the DB is documented as disposable/rebuildable in schema.sql, if that is the chosen route).

Distinct from #2455 (deleted comments resurrected by stale upserts — the monotonic guard's key, not the primary key's namespace) and #2456 (failed webhook deliveries unretryable — the deliveries table, not comments).

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/pm-stackpm-stack (Pad/Grist) in developing-today/code
    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
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 31, 2026
  4. 21 remaining items

  5. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    🤖 Agent note · picked up

  6. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    🤖 Agent note · done (triage verdict: CONFIRMED, fix already in flight)

    Verdict: CONFIRMED still live on current main 77f911fbfc5 (checked fresh 2026-11-03).

    • pm-stack/projector/schema.sql:47 on main still declares id TEXT PRIMARY KEY for pmp_comments with kind as a plain column — the bare-ID keying this issue files is unchanged on main.

    Fix path already exists — no new follow-up enqueued (would duplicate):

    • This issue was already triaged by queue task 4085 (2026-10-31, verdict CONFIRMED with a behavioural probe), which routed follow-up fix queue task 4413.
    • Task 4413's fix is open as PR fix(projector): scope comment identity by kind #3132 "fix(projector): scope comment identity by kind" (branch queue/4413-2694-comment-identity): comment identity becomes (id, kind), webhook deletions are scoped to their kind, legacy schemas are rejected before startup, and the PR reports bun test 48 passed including same-ID cross-kind collisions. Codex review on PR fix(projector): scope comment identity by kind #3132 completed with no findings; the only check shown is CodeRabbit (NEUTRAL, review-limit notice).
    • Remaining step is PR fix(projector): scope comment identity by kind #3132's own review/merge under task 4413, including the PR's stated one-way-door database procedure (stop writers, back up the whole DB — especially pmp_links — drop only pmp_v_subject_activity + pmp_comments, restart, reconcile). This triage does not merge another task's PR.

    Routing: none new — task 4413 / PR #3132 own the fix. This coverage triage (task 7962) completes as triaged.
    (Note: the user-token gh comment path hit the secondary rate limit this run; this note was posted via the developing.today GitHub App per the standing fallback order.)

  7. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 3, 2026
  8. developing-today commented on Nov 8, 2026

    @developing-today
    Contributor

    🤖 Agent note · picked up + triage verdict

    Triage verdict — task 12073 (lane R, 2026-11-07): REAL AND CURRENT — pmp_comments still keyed by bare GitHub ID on main; fix PR #3132 still open

    Fresh verification (the claimed/in-progress labels on this issue were stale leftovers from the 2026-11-03 triage — no live holder; this claim is the live one):

    Routing: enqueued task 12648 (pr_shepherd, dedupe key repo-health:v1:pr:developing-today/code:pr:3132) scoped to: rebase/revalidate PR #3132 (including the migration story for existing rows if the key changes), projector tests including a cross-kind same-ID collision test, Codex disposition, merge, and close this issue with the evidence. No competing implementation.

    🤖 Agent note · done

  9. added
    status/triagedTriaged and routed (see agent note)
    queue/queuedHas a queued task in the Supabase agent queue
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

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)queue/queuedHas a queued task in the Supabase agent queuesource/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobstatus/triagedTriaged and routed (see agent note)task/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