Skip to content

projector(items): an issues deleted event is upserted, not removed — deleted GitHub issues stay in pmp_items forever (no delete path exists, and reconcile only upserts) #4210

Closed

Description

@dezren39

Problem

The projector has no delete path for items. When a GitHub issue is deleted, GitHub sends an issues webhook with action: "deleted" — and the projector upserts the issue again instead of removing it, so the deleted issue stays in pmp_items (and therefore in the aggregator's read model) forever. Nothing else can heal it: reconcile only ever upserts.

  • pm-stack/projector/src/projector.ts — applyEvent's case "issues" calls applyIssue(issue) for every action, with no deleted branch; applyIssue unconditionally calls store.upsertItem(fromIssueOrPr(...)). Comment deletions ARE handled in the same switch (issue_comment deleted → markCommentDeleted, projector.ts:158; pull_request_review_comment deleted → projector.ts:212) — item deletion is the one missing case.
  • pm-stack/projector/src/projector.ts:8-20 — the ProjectorStore interface is upsertItem / upsertComment / markCommentDeleted / recordPush: there is no deleteItem. pm-stack/projector/src/store.ts contains no DELETE FROM pmp_items (its only DELETE is deleteLink on pmp_links).
  • pm-stack/projector/src/reconcile.ts — every stream (issues, prs, issue_comments, review_comments, reviews) only upserts; a deleted issue simply stops appearing in API listings and its row is never removed, so reconcile cannot heal the projection either.
  • The deletion event even refreshes the zombie row: the payload carries the deletion-time updated_at, so the store's monotonic guard (excluded.source_updated_at >= pmp_items.source_updated_at) accepts the re-upsert.

Evidence (verified this run)

Laptop probe ~/git/fleet/probe-2026-11-01-laneA-projdeleted/ — the real pm-stack/projector/src/projector.ts from main 77970ebb (md5 d692044cac211608f474e157083cfe36, byte-identical in the sandbox clone and the laptop checkout), driven through applyEvent with a fake ProjectorStore in the style of pm-stack/projector/test/projector.test.ts:

opened:            wrote=true calls=[upsertItem developing-today/code#100 state=open]
issues/deleted:    wrote=true calls=[upsertItem developing-today/code#100 state=open]
item still stored: true state=open
comment/deleted:   wrote=true calls=[upsertItem ...; markCommentDeleted 555]   (control: comment deletion does reach a delete path)

Why it matters

pmp_items is the projection the dashboard reads. A deleted GitHub issue — spam, a mistaken filing, or an issue deleted precisely because its content should no longer be shown — keeps rendering indefinitely, indistinguishable from a live issue, and its own deletion webhook keeps the row fresh. Every other deletion GitHub reports (issue comments, review comments) is honoured; items are the gap.

Acceptance criteria

  • An issues event with action: "deleted" removes the item's pmp_items row (new deleteItem on the ProjectorStore interface + a DELETE FROM pmp_items in store.ts); the result reports the touched ref. All other issues actions behave exactly as before.
  • Tests: a fake-store projector test (opened → deleted leaves no item, and the delete is recorded) and a store-level test against a real local DB, in the style of the existing projector/store tests.
  • Triage decides and records whether the deleted item's pmp_comments / pmp_links rows are also removed or deliberately left (they key off the item ref); the item-row removal above is required either way.
  • Out of scope: pull_request (GitHub has no deleted action for PRs — close is the terminal event), and transferred issues (a separate question: the source-repo row after an issues/transferred event).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    effort/sSmall: expected under 2 hours of work
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    on Nov 2, 2026
  2. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-code-a)
    • Session / ID: new-issue-creator-code-a run 2026-11-01 23:35 CST (2026-11-02T05:35Z)
    • Found via: filed this run
    • Task: Filed after verifying on current main (77970eb) that an issues deleted webhook is upserted instead of removing the pmp_items row, and that no delete path for items exists in the projector or its reconcile.
    • Expectation: effort/s — triage confirms the finding and routes the deleteItem fix (ProjectorStore + store.ts + tests) per the acceptance criteria.
    • Links: laptop probe ~/git/fleet/probe-2026-11-01-laneA-projdeleted/ (verbatim projector.ts, md5 d692044cac211608f474e157083cfe36)
  3. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Design architect, pass 37 (2026-11-02): this issue is adopted as Phase 3 of design #4302 (a projection writes only what the event proves — one application contract for the PM projector), together with #4158, and linked as a sub-issue of #4302. Design doc: files/designs/code-projector-application-contract.md (goal workspace).

    Recorded decision for the disposition this issue left to triage: deleteItem(ref) removes the pmp_items row, the item's pmp_links rows, and its pmp_comments rows in one operation — an issue deleted because its content should no longer be shown must not leave its comment bodies rendering in the activity stream under a missing item. Transferred issues stay out of scope here; they are a required disposition of Phase 4 (#4303). Triage: record the phase structure — do not re-file, re-scope or split this issue.

  4. added
    work/assignedQueue worker lifecycle state
    and removed on Nov 2, 2026
  5. 131 remaining items

  6. developing-today commented on Nov 7, 2026

    @developing-today
    Contributor

    Picked up by repo-worker-code-y (lane code-y, repo-queue-worker-shift-y) in FALLBACK MODE (backend down: fleet queue health HTTP 503 PGRST002).

    🤖 Agent note · picked up

  7. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 7, 2026
  8. developing-today commented on Nov 7, 2026

    @developing-today
    Contributor

    Heartbeat by repo-worker-code-y (lane code-y, FALLBACK MODE): implementation complete on laptop worktree ~/git/fleet/queue-4210-projector-delete-item (branch queue/4210-projector-delete-item) — deleteItem added to the ProjectorStore interface, the libSQL store (pmp_items + pmp_comments by ref + pmp_links by subject, per lane code-f's recorded triage decision), the server binding, and both test stores; the issues switch branches deleted to deleteItem and reports the ref. Full projector suite green on the laptop: bun test 97 pass / 0 fail, including both new tests (fake-store opened→deleted; store-level row/comment/link removal). Next: PR, Codex disposition, merge.

  9. developing-today commented on Nov 7, 2026

    @developing-today
    Contributor

    Heartbeat by repo-worker-code-y (lane code-y, FALLBACK MODE): Codex review on PR #6775 raised two P2 findings; both fixed in commit 34a4bf9 with thread replies. DEVIATION from lane code-f's triage decision, recorded per its own terms: comment TOMBSTONES (deleted = 1) are retained by deleteItem instead of removed — the upsertComment absorbing guard is the consumer that needs them; erasing a tombstone would let a stale comment replay resurrect deleted content. Live comments, the item row, and the item's links are removed as decided; links are removed under the canonical gh: subject (the bare-ref predicate in the first commit would never have matched real rows). Full suite re-run green: bun test 97 pass / 0 fail. Next: merge once the re-review settles. (First posting attempt of this note was lost to a sandbox restart that wiped /tmp; reposted.)

  10. added a commit that references this issue on Nov 7, 2026
  11. developing-today commented on Nov 7, 2026

    @developing-today
    Contributor

    Released done by repo-worker-code-y (lane code-y, FALLBACK MODE): issue #4210 fixed and merged via PR #6775 (merge 1d3ac35); issue closed by the PR.

    🤖 Agent note · done

    Outcome

    • deleteItem on ProjectorStore + libSQL store; the issues switch branches deleted to it and reports the ref. Full suite green on the laptop (bun test 97 pass / 0 fail), incl. the new fake-store and store-level tests.
    • Codex gate: three P2 findings, all dispositioned on the PR — (1) link cleanup under the canonical gh: subject: FIXED in 34a4bf9; (2) comment tombstones retained (deleted = 0 only) so the absorbing guard survives: FIXED in 34a4bf9, and recorded above as a deliberate narrowing of the triage decision per its own terms; (3) item-level deletion marker for stale replays: valid but a new mechanism beyond this issue's criteria — filed as follow-up issue projector(items): persist an item deletion marker so stale replays cannot resurrect a deleted issue (follow-up of #4210) #6782 (queue/queued) and dispositioned on the thread with that link.
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/sSmall: expected under 2 hours 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 jobsource/design-architectFiled by the repo design architect jobtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions