git:20260810.a39e221 to git:20260810.a58c34c

4 added, 2 removed. Audit A to A.

---
name: gh-issue-claim-coordination
- description: Coordinate GitHub issue pickup across parallel Claude Code sessions (or human + agent) using BOTH `assignees` and a `wip` label, so sibling sessions detect the claim and skip. Use BEFORE writing any code that closes / addresses a GitHub issue when there's any chance another session is working the same repo. Trigger on phrases like "pick up issue #N", "work on #N", "let's grab the next one from the backlog", "implement the bug-tagged issues", "start the next session", "before I start coding on this issue", "did anyone else pick this up?", "is anyone on #N?", "another session is working on…", "WIP label", "in-progress label", "issue lock", "agent claim", "claim the issue", "release the claim". ALSO trigger when the user explicitly runs multiple parallel sessions / worktrees on the same repo, when an agent is dispatched to "work through the open issues", or when about to file a PR with `Closes #N`. NOT for: filing new issues, commenting without claiming, reviewing PRs, single-session work with no parallel risk, or changes that aren't backed by an issue.
+ description: |
+ Before writing code for a GitHub issue when another session might take the same one, claim it
+ with BOTH an assignee and a `wip` label so a sibling sees it and skips. Trigger on "pick up
+ issue #N", "work through the open issues", "is anyone on this?". Not for filing or reviewing.
---
-
# GitHub issue claim coordination (assignee + label)
When multiple Claude Code sessions (or human + agent) work the same repo in parallel, two sessions can independently pick up the same issue, do parallel implementations, and collide at PR time — burning hours of duplicated work and forcing one branch to be rebased or abandoned.
**Default agent behaviour is silent.** An agent dispatched to "work on the bug-tagged issues" does not automatically check whether another session is already on issue #N. There's no built-in coordination unless this skill (or equivalent) runs at pickup.
This skill installs a 60-second protocol — preflight check, atomic claim, stale-claim sweep, release on completion — using two GitHub-native primitives so the claim is visible everywhere (issue list UI, `gh issue list`, dashboards, sibling sessions).
## Why both `assignee` AND a `wip` label?
They cost ~0 to set together and cover different failure modes:
- **`assignees`** is GitHub's canonical "who's on this" primitive. `gh issue list --assignee ""` cleanly enumerates unclaimed work; agents tend to surface assignees in their own context without prompting.
- **A `wip` label** is far more visible in the GitHub issue list UI (a coloured chip next to the title) and lets you find every active claim with one query: `gh issue list --label wip`. It also survives the case where someone reassigns the issue without thinking about coordination.
Belt and suspenders. If the assignee gets dropped on a bulk edit, the label still flags it; if the label gets dropped, the assignee still says it.
## Pickup protocol
### Step 0: Ensure the `wip` label exists (idempotent — runs every time)
Don't carry "did I do the setup yet?" state. The label create is cheap and noisy on success only the first time; on subsequent runs it just exits non-zero with "already exists", which `|| true` swallows. Bake it into every pickup so a fresh repo or a teammate's repo just works.
```bash
gh label create wip \
--description "Claimed by an active session — do not start parallel work" \
--color FBCA04 \
|| true
```
If this fails with a permissions error (rare; happens on repos where your account can't manage labels), fall back to assignee-only claim and tell the user.
### Step 1: Preflight (single command, returns JSON)
Before writing any code for an issue:
```bash
ISSUE=123 # the issue you're about to start
gh issue view "$ISSUE" \
--json number,state,assignees,labels,updatedAt \
--jq '{number, state, assignees: [.assignees[].login], labels: [.labels[].name], updatedAt}'
```
Read the result against this decision table:
| State + assignees + labels | Action |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPEN`, `assignees: []`, no `wip` label | **Free — claim it.** Proceed to claim step. |
| `OPEN`, assigned to you, has `wip` label | **Yours — resume.** No re-claim needed. |
| `OPEN`, assigned to someone else, OR `wip` label set by another | **STOP.** Surface to user with assignee + `updatedAt`. Do not start parallel work. |
| `OPEN`, has `wip` but `updatedAt` > stale window with no linked PR | Likely abandoned — see [Stale-claim sweep](#stale-claim-sweep) before claiming. |
| `CLOSED` | Issue is done. Confirm scope with user before reopening; the merged PR's diff may already cover what you were going to build. |
### Step 2: Claim (after preflight clears)
```bash
gh issue edit "$ISSUE" --add-assignee @me --add-label wip
```
These are independent edits — both should succeed (Step 0 already ensured the `wip` label exists). If only one succeeds because of a transient blip, retry the missing one before starting work. A half-claim is worse than no claim because it confuses future preflights.
### Step 2 (alternative): Surface a collision
When the preflight shows another session has the claim, **do not proceed**. Tell the user something like:
> Issue #123 is currently claimed by `@other-session` (`wip` label, last touched 2h ago at 2026-05-08T09:14Z). Want me to (a) wait, (b) work on a different issue, or (c) override the claim because you know that session is dead?
The user owns the override decision. They might know the other session crashed; they might not. Surfacing the timestamp + claimant lets them choose; do not unilaterally override.
## Stale-claim sweep
Sessions crash, get cancelled, get forgotten. Without a sweep, a stale `wip` label can lock an issue forever.
**Default rule:** a claim is stale if `updatedAt` is more than **24 hours** old AND no PR is linked. Adjust the window to your workflow (shorter for fast-moving repos, longer if multi-day issues are normal).
Find candidates:
```bash
gh issue list --label wip --state open \
--json number,title,assignees,updatedAt,url \
--jq '.[] | select((now - (.updatedAt | fromdateiso8601)) > 86400)'
```
(`86400` = 24 hours in seconds. Change to `43200` for 12h, `14400` for 4h, etc.)
When you find a stale claim, **ask the user before clearing it** — the assignee may have paused intentionally (waiting on an external dep, debugging out of band, etc.). If cleared, drop both:
```bash
gh issue edit "$ISSUE" --remove-assignee <stale-assignee> --remove-label wip
```
## Release on completion
GitHub auto-handles the happy path: a merged PR with a closing keyword (`Closes #123` / `Fixes #123` / `Resolves #123`) closes the issue and the assignee becomes irrelevant. The `wip` label, however, is **not** auto-removed by PR merge — and a CLOSED issue still showing `wip` clutters `gh issue list --label wip` and confuses future stale-claim sweeps.
After PR merge, drop the `wip` label:
```bash
gh issue edit "$ISSUE" --remove-label wip
```
The assignee can stay (it's useful history of who shipped the fix). Only the label needs cleaning.
### Partial-scope guardrail
If your PR only addresses **part** of an issue (`Addresses Part 1.3 of #500` rather than `Closes #500`), keep the issue claimed:
- Do **not** remove the `wip` label after merging the partial PR.
- Do **not** remove yourself as assignee.
- The claim continues until **all** in-scope parts ship.
GitHub's closing-keyword parser is permissive enough to auto-close the parent issue on partial PRs if the wording drifts (e.g. `Closes Part 1 of #500` will close #500 wholesale). The defensive PR-body phrasing ("Addresses Part X of #N", no closing keyword anywhere near `#N`) is independent guidance — see the project memory file `feedback_pr_partial_close_phrasing.md` if you have one.
## When NOT to use this skill
- **Single-session work** with zero parallel risk. The 60-second protocol is overhead with no benefit.
- **Filing new issues.** Claim happens at pickup, not creation.
- **Just reading or commenting** on an issue. The claim signals "I'm about to write code for this," not "I'm thinking about this."
- **Repos where your account can't be assigned** (rare; surfaces as a `gh issue edit` error). Fall back to label-only with a comment recording your session ID.
## Quick reference
```bash
# Pickup (Step 0: ensure label exists — idempotent, runs every time)
ISSUE=123
gh label create wip --color FBCA04 \
--description "Claimed by an active session — do not start parallel work" || true
gh issue view "$ISSUE" --json number,state,assignees,labels,updatedAt \
--jq '{number, state, assignees: [.assignees[].login], labels: [.labels[].name], updatedAt}'
# (read decision table; if free:)
gh issue edit "$ISSUE" --add-assignee @me --add-label wip
# Stale sweep (24h window)
gh issue list --label wip --state open \
--json number,title,assignees,updatedAt,url \
--jq '.[] | select((now - (.updatedAt | fromdateiso8601)) > 86400)'
# Release after merge (only the label; assignee can stay)
gh issue edit "$ISSUE" --remove-label wip
```
## Sister skills / memory entries
- **`feedback_track_partial_batch_filings.md`** — different problem (knowing what YOU filed when a sandbox cancels mid-batch). This skill prevents the upstream collision; that one cleans up after a different failure mode.
- **`feedback_coordination_framing_for_parallel_artifact_collisions.md`** — what to do AFTER two parallel artifacts have already collided. This skill prevents the collision; that one frames the recovery.
- **`feedback_pr_partial_close_phrasing.md`** — keeps GitHub's closing-keyword parser from auto-closing on partial PRs. Pairs with the partial-scope guardrail above.
- **`pr-conflict-site-regen`** — resolves rebase conflicts when parallel PRs collide on generated site files. Downstream of a missed claim; if this skill runs, that one fires less often.
## Reference-only siblings in this toolkit
These carry `disable-model-invocation: true`. They never appear in the skill
listing and the Skill tool refuses them, so the only way in is to open the file
with Read when one of these matches what you are looking at.
- [`prep-pr-close-keyword-auto-closes-issue`](../prep-pr-close-keyword-auto-closes-issue/SKILL.md) — a prep/planning PR's `Closes #N` closed the issue before the work was done
- [`executed-prompt-not-retired-sibling-redoes-it`](../executed-prompt-not-retired-sibling-redoes-it/SKILL.md) — an executed brief was not retired on both the file and its index, so a sibling session redid the work
- [`wip-branch-linter-revert-system-reminder-trap`](../wip-branch-linter-revert-system-reminder-trap/SKILL.md) — a linter or automation quietly reverted a deliberate wip-branch framing