Skip to content
Back to skills

Issue Dedup

ASecurity

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.

  • 10 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 12, 2026
ai-agentsgit

Works with

  • mcp

Security analysis

A100/100

Scanned September 12, 2026

npx -y skills add FriendlyInternet/nuxt-crouton --skill issue-dedup --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Issue Dedup?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Issue Dedup
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/friendlyinternet-issue-dedup/badge)](https://www.skillsdirectory.com/skills/friendlyinternet-issue-dedup)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.