Before creating a GitHub issue or epic, search for an existing related one and force a reuse / replace / new decision — so the issue-first flow can't silently mint a duplicate. The create-time half of the dedup gate (the pickup-time half is /issue-sanity-check). Use whenever you're about to open an issue/epic, or run /issue-dedup. Backed by a hard PreToolUse hook on issue_write create.
Scanned 9/12/2026
Install to Claude Code
npx -y skills add FriendlyInternet/nuxt-crouton --skill issue-dedup --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Issue Dedup?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/friendlyinternet-issue-dedup)More formats (shields.io, HTML) on the badges page.
---
name: issue-dedup
layer: method
description: Before creating a GitHub issue or epic, search for an existing related one and force a reuse / replace / new decision — so the issue-first flow can't silently mint a duplicate. The create-time half of the dedup gate (the pickup-time half is /issue-sanity-check). Use whenever you're about to open an issue/epic, or run /issue-dedup. Backed by a hard PreToolUse hook on issue_write create.
allowed-tools: mcp__github__search_issues, mcp__github__list_issues, mcp__github__issue_read, mcp__github__issue_write, mcp__github__add_issue_comment, Read, Grep
---
# Issue Dedup — search before you create (HARD GATE)
The repo is **issue-first**: the first action for any initiative is opening the tracking
issue. The trap that creates is **duplicate / colliding issues** — a teammate (or a past
you, in an ephemeral session) already opened the epic, and a fresh agent dutifully opens a
second one. `github-tasks` already *says* "search before creating," but that was a soft,
skippable step. **This skill makes it a gate**, and a PreToolUse hook
(`require-issue-dedup.mjs`) backstops it so a `create` can't land without proof a search ran.
> Scope split: this is the **create-time** gate (don't mint a duplicate). The **pickup-time**
> gate — "should this issue even be done?" — is the sibling **`/issue-sanity-check`** skill.
> Both are sub-issues of epic #297.
## When this fires
Any time you're about to call `mcp__github__issue_write` with `method: create` — a new
epic, sub-issue, or standalone issue. Run the procedure below *first*.
## Procedure
### 1. Extract search terms
Pull 2–5 distinctive keywords from the intended title/body — the *nouns* of the work
(feature name, package/app, the thing being changed). Drop filler ("add", "fix", "the").
### 2. Auto-search open **and** recently-closed work
Stale-but-closed issues matter: the #257/#265 blog case duplicated *closed* epics. Search
both:
```
mcp__github__search_issues query: "repo:FriendlyInternet/nuxt-crouton <keywords>"
mcp__github__search_issues query: "repo:FriendlyInternet/nuxt-crouton in:title <keyword>"
mcp__github__list_issues labels: ["epic"] # scan epic titles for overlap
```
Run a couple of phrasings — exact-title and broad-body — and include `state:closed` in at
least one. **Keep results in-context:** these calls overflow easily (a bare `list_issues
labels:["epic"]` came back at ~140k chars), so pass **`minimal_output: true`**, a small
**`perPage` (5–10)**, and prefer **`in:title`** filters — only widen if a narrow query misses.
If output still overflows, narrow further rather than slicing the blob out-of-band.
### 3. Surface the matches to the human (number · state · why-similar)
Present a short table of candidates — **never** silently proceed:
| # | State | Title | Why it might be the same |
|---|-------|-------|--------------------------|
| #297 | open | Stale-epic dedup… | same feature — the dedup gate |
| #265 | closed (completed) | POC pipeline preview URL | adjacent, not the same |
If a candidate's closing comment cites artifacts (writeups, PRs) that **don't exist** in the
repo, flag it — that's the #297 smell (a stale close hiding undone work).
### 4. Force a **reuse / replace / new** decision — this is the hard stop
Do not create until one is chosen:
- **Reuse** — a match already covers this. **Stop. Don't create.** Continue *that* issue
(assign yourself, `status:in-progress`). This is the whole point of the gate.
- **Replace** — a stale/closed issue should be superseded. Link it in the new body
(`Supersedes #NN`) and, if it's open, close it `not_planned` referencing the replacement.
- **New** — genuinely nothing matches (or matches are only adjacent). Proceed to create.
**How to ask** (the on-match signal):
- **Interactive session** → ask in chat with `AskUserQuestion` (reuse / replace / new) and
wait. This is the cheapest stop.
- **Autonomous / pipeline** (no human in the loop to block on) → you may create the issue,
but **immediately post a reply comment** on it naming the suspected duplicate(s) and asking
the owner to confirm reuse / replace / keep — assigned to them so it surfaces. A created
issue with an unresolved dup question is honest; a silent duplicate is not.
### 5. Record the dedup result in the body (REQUIRED — the hook checks for it)
Every created issue body MUST end with a one-line **dedup attestation** — it's both the
audit trail and the token the backstop hook greps for. Format:
```
_Dedup-checked: searched «keyword, keyword» → no match_
```
or, when something related turned up:
```
_Dedup-checked: searched «dedup, issue» → related #297 (open), #800 (open); new because this is the create-time half_
```
The literal prefix `Dedup-checked:` is what the hook requires. Omit it and
`require-issue-dedup.mjs` blocks the `create` with a reminder to run this skill.
## The backstop hook
`.claude/hooks/require-issue-dedup.mjs` is a PreToolUse gate on `mcp__github__issue_write`:
- Only gates `method: create` — `update` and everything else pass untouched.
- Blocks (exit 2) when the `body` lacks a `Dedup-checked:` line.
- Mirrors `require-comment-provenance.mjs` exactly (body-content check, no state).
It's a *backstop*, not the brain — it can't tell whether you really searched, only that you
attested. Do the real search (steps 1–4); the marker is your signed receipt.
## What this is not
- Not the pickup gate — that's `/issue-sanity-check` (#800).
- Not a substitute for judgement — "related" ≠ "duplicate". Adjacent issues coexist; only a
genuine same-scope overlap is a reuse/replace.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!