Skip to content

Distinguish closed-but-unmerged PRs from shipped work on the kanban #14

@aptracebloc

Description

@aptracebloc

Confirmed the problem and mapped the auto-rules. Here's the lay of the land and a few options:

Why it happens

Your project has 6 active workflows. The relevant ones:

# Workflow Trigger Action
7 Item closed any issue/PR closed sets Status = Done
8 Pull request merged PR merged sets Status = Done

Workflow #7 fires regardless of whether a PR was merged or just closed — so abandoned/superseded PRs land in Done alongside shipped work. (Quick scan: at least 18 closed-but-unmerged PRs are currently sitting in Done — including some of yours like .github#1, tracebloc-py-package#40, and the dependabot bumps backend#578-588.)

Issues have the same problem in two flavors: closed-as-completed vs closed-as-not_planned both also land in Done.

Current Status options: Backlog → Ready → In progress → In review → Validation → Done.

Options

Option A — Add a "Cancelled" status, repurpose built-ins (lowest effort)

  1. Add Status option Cancelled (or Won't ship).
  2. Change workflow #7 "Item closed" → set Status = Cancelled.
  3. Keep workflow ci: add reusable set-pr-status workflow #8 "Pull request merged" → set Status = Done.

How it routes:

  • ✅ PR merged → #7 sets Cancelled, then ci: add reusable set-pr-status workflow #8 overrides to Done
  • ✅ PR closed unmerged → #7 sets Cancelled
  • ❌ Issue closed-as-completed → ends up in Cancelled (wrong)
  • ✅ Issue closed-as-not_planned → Cancelled

Works perfectly for PRs (~98% of your board today). Mis-routes completed issues — survivable but not great. Also depends on workflow #8 firing after #7, which is the observed behavior but not formally guaranteed.

Option B — Custom GitHub Action that routes properly (cleanest)

  1. Add Status option Cancelled.
  2. Disable built-in workflow #7 ("Item closed").
  3. Add a small repo-level Action (in .github) listening to pull_request: closed and issues: closed, which calls the GraphQL updateProjectV2ItemFieldValue mutation to set:
    • PR merged → Done
    • PR closed unmerged → Cancelled
    • Issue state_reason: completed → Done
    • Issue state_reason: not_planned → Cancelled

Handles all four cases correctly. ~50 lines of Action code. Costs you maintaining one workflow file, but you already have .github infrastructure for this kind of thing (advance-deploy-env, set-pr-status are exactly this shape).

Option C — Add a separate "Outcome" field, leave Status alone

Add a new single-select field Outcome with values Shipped / Cancelled / Superseded, populated by an Action on close. Status stays as-is (everything closed = Done), but views can group/filter by Outcome.

Less disruptive to existing views and saved filters, but the kanban swimlanes still mix shipped and abandoned work — you have to remember to add an Outcome filter to "what shipped" reports.

Option D — View-only filter (no schema change)

Create a saved view "Done (shipped)" that filters to status:Done is:merged. Cheap, but doesn't change the underlying truth — abandoned PRs still pollute counts and most views.

My recommendation

Option B, with Option A as a fast interim if you want it landed today. Option A gets you 95% of the value in a 2-minute config change; Option B is the proper fix and is in keeping with the .github reusable-workflow pattern you've already been rolling out across repos.

Want me to draft the Action for Option B, or just flip the workflow for Option A?

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions