Create GitHub issues from a plan, bug, finding, audit or PRD, whose titles and bodies a stranger can act on. Use when filing issues or turning work into issue-sized slices. Routes unresolved decisions to a plain-language Question/Why/Questions/Done when shape; writes each issue in its verifier's language as Outcome plus Done when; sizes issue sets to independent slices; labels by issue kind with bug priority; wires real sub-issue, blocked-by, and duplicate edges rather than prose references; ...
Installs into .claude/skills of the current project.
Are you the author of To Issues Clearly?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/checkpickerupper-to-issues-clearly)
---
name: to-issues-clearly
description: "Create GitHub issues from a plan, bug, finding, audit or PRD, whose titles and bodies a stranger can act on. Use when filing issues or turning work into issue-sized slices. Routes unresolved decisions to a plain-language Question/Why/Questions/Done when shape; writes each issue in its verifier's language as Outcome plus Done when; sizes issue sets to independent slices; labels by issue kind with bug priority; wires real sub-issue, blocked-by, and duplicate edges rather than prose references; and offers an existing or new milestone when the issues serve one deliverable."
short_description: "Turn plans, findings, bugs, and PRDs into clear, verifiable GitHub issues."
allow_implicit_invocation: true
---
# To issues clearly
Turn a plan, bug report, audit finding, or PRD into live GitHub issues that explain the outcome clearly enough for another person to implement and verify.
The tracker is the publication surface. Draft in the conversation, then create or edit the issue directly with GitHub. The skill supplies judgment and a repeatable shape; GitHub supplies the issue number, labels, and relationships.
## Choose the issue shape before writing
The title and body depend on what the source is asking for. Do not force every issue into a defect-shaped template.
- **Decision:** a product, policy, workflow, or boundary choice is unresolved. Use an imperative title such as `Decide where customers book online` and the decision template below.
- **Change, fix, or capability:** the issue exists to make the product do something. Use an imperative desired-outcome title such as `Show seller-linked colour photos in the storefront before vision processing completes` or `Keep a failed upload available for retry`.
- **Reported defect or contract failure:** the issue exists to record and investigate what is wrong now. Use the observed-action-and-consequence title shape below.
- **Wide refactor:** one mechanical change has a cross-codebase blast radius. Use the expand/migrate/contract sequence below.
Classify first. A decision issue asks people to choose; an implementation issue tells someone what behavior to build; a defect issue says what behavior is wrong now.
## Titles that survive a cold read
The title is the first test of whether the issue is understandable without the source plan.
### Whose words
Write every title and body in the **verifier's language**: the words of whoever will check the work. A behavior change is verified by a product user, so its title uses domain words. A code-internal change is verified by an engineer, so its title names the code object and the operation (`Delete the empty server cue and equipment stubs`, `Split the server character type from client presentation`).
The stranger is a skilled engineer new to this repository. They understand types, adapters, imports, and checks; they do not know your names yet. Define each repo-specific noun where they first meet it — file plus one-line meaning, in prose — and never a glossary section.
Name the operation and the primary object; details live in the body. A title that needs a semicolon is two titles or a spec wearing a title's clothes — cut it.
### Decision titles
Use this shape only when the choice is genuinely unresolved:
Decide where customers book online
Choose how a business reaches its booking page
Define what a guest can see before signing in
Name the decision in terms of the person, business, or product surface it affects. Replace architecture words such as `boundary`, `projection`, `surface`, and `route map` with the concrete choice they describe. Do not force a system consequence into a decision title, and do not silently choose an option while rewriting the issue.
Read a decision title cold and answer: **what choice is unresolved, and who does it affect?** If the answer is not clear, make the actor or product object concrete.
### Behavior and defect titles
Everything else here is ordinary. This part is not, because it fails constantly and always looks fine at the time.
#### The shape
**A behavior title has two halves: what somebody wrote, asked for, or did — then what the system did with it.**
A config can set retries to five, and the client gives up after three
A user can mark an invoice paid, and the balance does not move
A migration can declare a rollback, and it is never run
A CLI flag can ask for JSON, and it prints a table
The first half does the work. It forces you to name something a real person actually does, and you cannot write it about a mechanism, because mechanisms are not written by anyone. Most bad titles die right there — there is nothing to put in the first half.
When nobody wrote anything and the thing simply misbehaves, the first half is the action that sets it off:
Deleting the last admin leaves the account with no owner
Retrying a failed upload uploads it twice
Not a state of affairs, and not a description of code. Something a person does, then what came of it.
For a reported user-visible defect, the action must be the interaction a person can perform or the state transition they can see. Never make the selector, rule, token, cascade, or other mechanism the actor. `Hovering a primary button makes its label unreadable` is the title; the style rule that causes it belongs in the body.
For a change, fix, or capability issue, use the desired outcome instead: `Show seller-linked colour photos in the storefront before vision processing completes`. Start with the verifier's verb: `Show`, `Allow`, `Keep`, `Prevent`, the domain's own verb — or the code operation (`Split`, `Delete`, `Narrow`) for code-internal work. Do not narrate the current failure in the title when the issue is asking someone to change it; open `## Outcome` with it instead.
#### Finding translation
For every defect or audit finding, write these facts before writing the issue:
1. **Observed behavior:** the action or input and the result someone can see or reproduce.
2. **Impact:** what becomes unusable, misleading, or blocked.
3. **Cause:** the technical reason for that result.
4. **Prevention and proof:** the invariant, guard, fix, and evidence.
Open `## Outcome` from **Observed behavior** and **Impact** (`X does Y, so ...`), then state what should be true instead. Choose the title from the issue intent: the desired outcome for a change, fix, or capability issue; **Observed behavior** and **Impact** only for a reported defect; the decision shape for a decision. Put **Cause** and **Prevention** in `## Cause and required change`, and **Proof** in `## Evidence`. This is a separation of facts, not a list of words to ban.
Regression example:
Bad: A state rule recolors the surface while the ink stays owned by another rule
Good report: Hovering a primary button makes its label unreadable
Good change: Make hovering a primary button keep its label readable
Bad: A seller binds a photo to a colour, and the storefront hides it until a vision model has looked at it
Good: Show seller-linked colour photos in the storefront before vision processing completes
Before publishing, ask: **Can a stranger reproduce or observe the problem from the title alone?** If not, rewrite it around the action and observable result.
#### The output check
**For a behavior or defect title, write the title with your rule, then read it cold and answer: what is broken?**
If the honest reaction is *ok, and?*, it has failed — even when it breaks none of the rules below. This is what catches the titles that read fine while the reasoning is still in your head.
These four shipped from an earlier run of this skill, and every one of them passes every ban in the next section:
A cosmetic's access route is the only fact its availability needs
Adding a tag to a fighter actually adds it
A formula reads from the caster or the target as part of what it is
Suppressing a tag either works or cannot be declared
Not one has a first half. Not one survives "and?". A ban list cannot catch these, because banning is a way of saying what not to write, and there is always an infinite amount left over. The shape is what closes it.
#### The backstop
The shape and the stranger test catch most of it. These constructions still get through, so they are refused outright:
| Never write | Why it fails |
|---|---|
| "…actually does X" | Sarcasm. Scoring a point instead of naming a defect. |
| "…as part of what it is" | Type vocabulary in a domain costume. Says nothing. |
| "X is the only fact Y needs" | A conclusion from reasoning the reader did not hear. |
| "X either works or cannot be declared" | Two outcomes joined by an invisible rule. |
| "X is an answer a caller can act on" | Abstract. Name the answer and who is stuck without it. |
| "X forgets / remembers / knows Y" | Code personified. Code does not forget. |
**No bare abstract noun as the subject.** "Availability states the same fact four times" — availability of *what*? Name something the reader can picture.
**No word with two readings in the domain.** "An author can contradict it" — a person writing content, or an in-world author? If it can be misread, replace it.
**A code-internal defect names its object.** Say what somebody can wrongly write and what becomes of it, using the symbol's name: the verifier is an engineer, and the name is the fastest pointer to the fault. Define the symbol where it first appears in the body.
#### Worked corrections
These come from one game codebase. The shape does not.
A cosmetic's access route is the only fact its availability needs
-> An item can say it is a drop and unselectable at the same time
Adding a tag to a fighter actually adds it
-> Dying does not mark a fighter dead
A formula reads from the caster or the target as part of what it is
-> A formula can say it reads from the caster, and the game throws
that away and decides again
Suppressing a tag either works or cannot be declared
-> An effect can declare "keep this status off them" and the game
ignores it
An error and a warning reach the output differently
-> Code can report an error, and it prints exactly like a warning
The last one is worth studying: it described the *desired* state as though it were the defect. A title names what is wrong now. What you want instead belongs in **Outcome**.
A decision title needs a different correction:
Decide patient-facing online-booking site boundary vs the organization website
-> Decide where customers book online
The first title makes the reader decode an architecture boundary. The second names the choice in the language of the person who needs the result. The decision body still records the options, constraints, identity flow, empty states, and lifecycle rules that an implementer will need later.
#### The good sentence is usually already written
When a title fights you, look at how you described the issue in prose — in the conversation, in the Outcome section, in a commit message. That sentence is usually already in the shape, because explaining something to a person forces both halves out of you. Prefer it over anything composed against this section.
## Process
### 1. Gather the target
Identify the repository and the source material. Read an existing issue, plan, or evidence file when one is supplied. Explore the codebase only when the issue needs a fact the conversation does not establish.
Search existing issues for a duplicate before creating anything. **Read the candidates rather than matching on words** — an issue asking for the opposite change is not a duplicate, it is the reason this one exists, and the new issue should reference it.
Reuse an existing issue when it already represents the same outcome; update it when the user asked to reshape it.
List the repository's open milestones with their descriptions and due dates:
~~~sh
gh api repos/OWNER/REPO/milestones --jq '.[] | "\(.number)\t\(.title)\t\(.due_on // "no due date")\t\(.description)"'
~~~
### 2. Decide which kind of issue each one is
Ask these in order before writing any title:
1. **Is a choice still unresolved?** If yes, this is a decision issue. State the choice and the people or product surface it affects; do not turn it into an implementation issue.
2. **Is the requested work to make, fix, prevent, or preserve a behavior?** If yes, this is a change issue. Title the desired outcome with an imperative verb in the verifier's language, even when the source also describes a current failure.
3. **Is the source only reporting or investigating what is wrong now?** If yes, this is a reported defect. Title the observable action and consequence: `Retrying a failed upload uploads it twice.`
For a reported contract or type failure with no user-visible symptom, the first half is what somebody can wrongly write: `A config can set retries to five, and the client gives up after three.`
When a source contains both a failure and a fix, classify it by the requested deliverable, not by the failure's wording.
Never write a behavior title for a decision with no chosen behavior. That is where invented poetry comes from: you reach for a user-facing sentence, there isn't one, and you produce something that sounds like a sentence but names nothing.
An issue whose only symptom is "the type permits nonsense" is a real issue. Say so plainly and stop.
### 3. Shape the issue set — err on granular
One issue per outcome that can be implemented, reviewed, and closed on its own.
**When in doubt, split.** Two issues that turn out to be one merge in seconds. One issue that turns out to be four is discovered halfway through implementing it, by somebody who now has a half-finished branch and no way to land any of it. The costs are not symmetrical, so the tie goes to splitting.
**Count the acceptance criteria — that is the granularity check.** Four to six is an issue. More than eight is a system wearing an issue's clothes, and the split is usually already visible: each cluster of criteria that could be built and reviewed alone is its own issue.
Split when two outcomes could land separately, when two parts would be reviewed by different people, or when one part could ship while the other waits on a decision. Keep observations together only when they share one end state and one review.
For a **decision issue**, keep the decision and the questions needed to make it together. Do not make one issue for every question in the decision body. Split only when the choices can be made independently, have different owners, or unblock different implementation work. Once a decision is made, its implementation is a separate issue or child issue unless the user explicitly asks for a decision-and-build issue.
**Cut each issue vertically, not by layer.** One issue is a narrow but complete path through everything it touches — the shape, the code that reads it, the callers, the tests. An issue that is only the schema change, with the callers in a second issue, cannot be verified or landed on its own.
**Size each one to a single fresh context window.** Someone picks it up knowing nothing about today's conversation. If finishing it needs more than they can hold at once, it is two issues.
**Apply the atomicity test before finalizing the split.** Can each issue land green on its own? If two slices only make sense together and cannot land separately, they are one issue. Splitting what must land atomically adds tracking overhead without reducing risk.
**Look for the prefactor and file it first.** *Make the change easy, then make the easy change.* When one preparatory change would make three others straightforward, that is its own issue and it blocks them. Finding it after filing the three is finding it too late.
Use words a maintainer would recognise. Keep file paths and proposed APIs out of the title unless the user has already made that decision.
### 3a. The wide refactor is the exception to vertical slicing
A **wide refactor** is one mechanical change — rename a field, retype a shared symbol, split a union — whose blast radius fans across the whole codebase. A single edit breaks hundreds of call sites at once, so no vertical slice can land green and forcing one produces an issue nobody can finish.
Sequence it **expand → migrate → contract**, as separate issues:
1. **Expand.** Add the new form beside the old. Nothing breaks, because everything still uses the old one. This issue lands green on its own.
2. **Migrate**, in batches sized by blast radius — per package, per directory, per feature. **Each batch is its own issue, blocked by the expand.** CI stays green batch to batch because the old form still exists.
3. **Contract.** Delete the old form once no caller remains. One issue, **blocked by every migrate batch.**
When even a batch cannot stay green alone, keep the sequence but let the batches share an integration branch, and have them all block a final integrate-and-verify issue. Green is promised only there — say so in that issue rather than implying each batch is independently green.
Spot one by asking: *would doing this in one commit break call sites in files this issue does not name?* If yes, it is a wide refactor, whatever it looks like.
If the breakdown is obvious, publish it without ceremony. Ask one focused question only when an unresolved choice changes the issue count, scope, or relationship tree.
### 3b. Place the set in a milestone
A milestone groups issues that must all close for one deliverable to ship: a release, a launch, a cutover, a dated commitment. A parent issue groups the slices of one outcome; a milestone groups the outcomes one deliverable needs. Decide each issue's milestone from that definition:
- **Existing milestone.** Propose it when the milestone's description names a deliverable this issue is required for. Read the description, not the title alone: a title that shares a word with the issue is not evidence.
- **New milestone.** Propose one when the issues together deliver something a person can ship or announce, no open milestone covers it, and closing them all marks that deliverable done. Title it as the deliverable in the verifier's language (`Venues take bookings without Central Admin`). Write a one-sentence description of what is true when it closes. Set a due date only when the user or the source gave one.
- **No milestone.** A standalone fix, chore, or decision that no deliverable waits on stays out of milestones.
An issue belongs to at most one milestone; when two fit, propose the one whose deliverable ships first and say why.
**Push back on catch-all milestones.** A milestone means done only when someone can check that its deliverable shipped. Flag a milestone as a bucket when its description lists themes or says "everything" (`Everything after the KYC launch: products, fund pages, subscriptions...`), or when it holds more than 50 open issues. Tell the user, propose a deliverable-sized milestone for the issues being filed instead, and leave the issues already in the bucket where they are unless the user asks to move them.
### 4. Write the body
#### Decision and product-choice issues
Use this shape when the source asks where, whether, or how a product behavior should be owned or exposed:
~~~markdown
## Question
When [person or organization] wants to [goal], where or how should [the product behavior] work?
Choose one:
- [Concrete option in domain language]
- [Concrete option in domain language]
- [A combined option, only if it is genuinely distinct]
## Why we need this decision
[Name what cannot be built, tested, communicated, or launched until the choice is made. State the known constraints and downstream work without prescribing an implementation.]
## Questions to answer
- [Entry point, ownership, or address question, when relevant]
- [Anonymous, signed-in, or privacy boundary question, when relevant]
- [Source of truth, lifecycle, or duplicate-record question, when relevant]
- [Missing, empty, unavailable, or error-state question, when relevant]
## Done when
- [ ] One option or policy is chosen and written in product language.
- [ ] The affected people, surfaces, and boundaries are specified.
- [ ] The relevant guest/authenticated, failure, empty, and privacy states are clear.
- [ ] The resulting data or workflow joins the existing lifecycle without a parallel record.
- [ ] The downstream issue can be built without guessing about this decision.
~~~
Use only the questions and criteria relevant to the decision. The checklist is a prompt for missing boundaries, not boilerplate to paste into every issue. If the source does not establish a fact, turn it into a question or constraint; do not invent the answer.
Make the options concrete and mutually distinguishable. Keep the choice open in the issue: `Choose one` records the decision to be made, not a recommendation the skill invented.
Do not prepend the generic `Outcome` and `Done when` sections to a decision issue. `Why we need this decision` explains the consequence of waiting, and its own `Done when` defines the decision's completion.
#### Defect, capability, and refactor issues
~~~markdown
## Outcome
What done looks like, in one short paragraph. When a current failure
motivates the change, open with it (`X does Y, so ...`), then state
what should be true instead.
## Cause and required change
- **Written at:** the file and line where the bad state is written.
- **Class:** the class of bad state, stripped of this incident's names.
- **Required change:** the exact change that makes the class unwritable,
including every type, function, file, or path that gets deleted.
## Done when
- [ ] Something checkable that shows it is done.
- [ ] The failure, boundary or empty case, when there is one.
- [ ] The old bad state is no longer reachable through the supported path.
## Evidence
Only for a defect that exists now: where it is, and the quoted code that proves it.
~~~
The verifier's language governs the body too. It is a report, not a case being argued.
**Every criterion is something the verifier can check.** For a behavior issue that means watching the product do the thing. For a code-internal issue it means an engineer-observable check: the typecheck passes, a `grep` for the deleted symbol returns nothing, the suite is green with no behavior diff. Someone must be able to close the issue by running the check and seeing the result — not by reading the diff and agreeing with it.
| Checkable | Not checkable |
|---|---|
| A fighter killed by the death effect reads as holding the dead tag | Add tag handling |
| An item marked drop-only and unselectable no longer compiles | Make availability a proper union |
| Buying a second hour while one is running ends four hours from now, not two | Fix the extend logic |
| No server file imports the client presentation shape | Separate the concerns properly |
Three rules that catch most bad criteria:
- **Name what the verifier needs to run the check.** A symbol, file, or command the check depends on belongs in the criterion; an implementation guess somebody has not chosen yet does not.
- **One criterion, one observation.** A criterion with "and" in it is two criteria, and half of it will be skipped.
- **Include the failure and the empty case.** "The old bad state is unreachable" is the criterion that stops an issue closing while the defect still has a back door.
Name file paths where the verifier needs them to find the check. A path that guesses at code nobody has written yet rots between filing and implementing — describe the behaviour and let the implementer find the file.
Evidence is for when the issue asserts something is broken **right now**. A claim nobody can check is worse than a path that might move, so a finding cites where it is and quotes the code that proves it. Say which commit or day it was read on — Evidence is a snapshot, not a live pointer.
An issue describing work that does not exist yet has no Evidence section at all. It has nothing to cite.
`## Cause and required change` carries the fix-the-class result, and every reported defect and every change or fix to existing behavior has one. Name where the bad state is written, not where it is noticed; name the class without the incident's nouns; and list the deletions explicitly, because a required change that only adds leaves the old path open. A capability with no existing code path replaces the section with the place the new behavior goes and anything it supersedes.
#### The verifier-language rewrite pass
Apply this pass to every issue, and apply it especially when the source is a technical plan or audit:
1. Name the actor, action, object, and consequence in the verifier's language — domain words for behavior, code words for code-internal work.
2. Define each repo-specific noun where it first appears: file plus one-line meaning, in prose.
3. Remove YAML keys, route-map scaffolding, and implementation guesses from the main prose. Keep a machine-readable key only when known repository automation requires it.
4. Preserve issue links and established terms when they carry useful context; explain an unavoidable term the first time it appears.
5. Translate competitor, reference-product, or screenshot analysis into behavior and constraints. Omit the reference brand, palette, and route-map detail unless it affects the decision or belongs in Evidence.
6. Turn unresolved choices into explicit options and questions. Never hide a decision inside the outcome or resolve it by implication.
7. Add only facts grounded in the source or evidence. A useful new boundary question is allowed; an invented rule is not.
The final draft should stand on its own. A reader should understand the verifier, the change, why it matters, what must be answered, and how completion will be checked without opening the source plan.
### 5. Put the breakdown to the user before publishing anything
**Nothing is created until the user has seen the set.** An issue published wrong has to be edited, and a set published wrong has to be edited eleven times.
Show a numbered list. For each: **title**, **kind label** (and priority, for a bug), **blocked by**, **milestone**, and **what it delivers** in one line. Above the list, show any new milestone with its title, description, and due date, and name the existing milestones you chose, each with the line of its description that justifies it. Then show the complete draft body for each issue, including the decision template when the issue is a decision. A title-only breakdown is not enough for the user to review the language or scope.
Then ask three things:
- Is the granularity right — too coarse, too fine?
- Is each blocking edge real, or is it just ordering?
- Should any of these be merged or split?
- Is each milestone placement right, and should the proposed new milestone exist?
Iterate until they approve. Read the titles back to yourself here too (step 8) — before publishing is when it is cheap.
Skip this only when the user has already approved a breakdown in the conversation, or asked explicitly for a single issue.
### 6. Publish, blockers first
Create any approved new milestone first, then read its number back:
~~~sh
gh api repos/OWNER/REPO/milestones -f title="..." -f description="..." --jq .number
~~~
Add `-f due_on=YYYY-MM-DDT00:00:00Z` only when a due date was approved. Create a milestone only after the user approved it in step 5.
Create issues in dependency order — anything that blocks something else goes first — so a child can reference a real number instead of a placeholder.
~~~sh
gh issue create --repo OWNER/REPO --title "..." --body-file ISSUE_BODY.md --label LABEL --milestone "MILESTONE TITLE"
~~~
`--milestone` takes the milestone's title. To place an existing issue, use `gh issue edit NUMBER --repo OWNER/REPO --milestone "MILESTONE TITLE"`. Verify placement afterwards with `gh issue view NUMBER --repo OWNER/REPO --json milestone`.
Apply the tracker's agent-pickup label (commonly `ready-for-agent`) unless told otherwise. These issues are written to be picked up cold; that is what the label says.
Read the repository's existing labels first and use them. **Never invent a label.** If none fits, publish without it and report the gap — the user decides whether a new label exists.
**The kind label follows the issue shape from step 2.** Only a reported defect that exists now gets the bug label (`kind:bug`, `bug`, or the repo's equivalent). A change, fix, or capability gets the repo's change, feature, or tech-debt label; a decision gets the decision label when one exists. A fix for a known bug is a change issue: the bug label on it inflates the bug count a zero-bug policy tracks. When the repository uses GitHub issue types, set the matching type with `--type` as well.
**Every bug carries a priority when the repository has a priority scheme.** Look for one in the labels (`bug:high` and `bug:low`, `priority:*`, `P0`–`P3`). When one exists, file no bug without a priority from it; when the source does not establish the priority, ask in step 5.
`gh issue create` takes repeated `--label` flags. Building them in a shell loop is where they silently go missing; `read -ra` is not portable to zsh. Verify the labels landed afterwards rather than assuming.
For an existing issue, edit it and preserve its history. Do not create a second issue because the wording changed.
### 7. Wire relationships natively — prose is not a link
**Writing "Part of #189" in the body does not create a relationship.** It creates a sentence. The tracker still shows an orphan, no board groups it, no query finds it, and closing the parent does not surface the child. Every relationship you state must exist as a native GitHub edge.
So: if you write it in prose, you must also create the edge. If you cannot create the edge, say so in the report — never let a prose reference stand in for one and call the tree wired.
Create the edge after both issues exist, using the issue database id:
~~~sh
# sub-issue (parent -> child)
gh api repos/OWNER/REPO/issues/PARENT/sub_issues -F sub_issue_id=$(gh api repos/OWNER/REPO/issues/CHILD --jq .id)
~~~
Note `-F`, not `-f`. `-f` sends the id as a string and the API rejects it as not an integer.
A blocker is genuine only when the child cannot start, or cannot meet its criteria, until the blocker lands. Ordinary sequencing is not a blocker, and "related" is not a parent.
Create a "blocked by" edge at creation or afterwards; each flag takes issue numbers or URLs:
~~~sh
gh issue create --repo OWNER/REPO ... --blocked-by 1138,1139
gh issue edit 1164 --repo OWNER/REPO --add-blocked-by 1161
~~~
The REST equivalent takes the blocker's database id: `gh api repos/OWNER/REPO/issues/BLOCKED/dependencies/blocked_by -F issue_id=$(gh api repos/OWNER/REPO/issues/BLOCKER --jq .id)`.
**When one issue replaces another**, record it natively:
- One-to-one, same outcome: `gh issue close OLD --repo OWNER/REPO --duplicate-of NEW`. GitHub shows the duplicate link on both issues.
- Split into several, or reshaped beyond the same outcome: GitHub has no "replaced by" edge. Open each replacement with `Replaces #OLD` in its body, so the old issue's timeline lists every replacement, then `gh issue close OLD --repo OWNER/REPO --reason "not planned" --comment "Replaced by #A and #B"`. Move any sub-issues and blocked-by edges from the old issue onto the replacements.
**Never close or edit a parent issue.** Wiring a child under it does not give you licence to touch it. If the parent's own text is wrong, say so in the report and let the user decide.
**Verify every edge by reading it back**, not by trusting the create call:
~~~sh
gh api repos/OWNER/REPO/issues/PARENT/sub_issues --jq '.[] | "\(.number) \(.title)"'
gh api repos/OWNER/REPO/issues/BLOCKED/dependencies/blocked_by --jq '.[] | "\(.number) \(.title)"'
gh api graphql -f query='{repository(owner:"OWNER",name:"REPO"){issue(number:OLD){stateReason duplicateOf{number}}}}'
~~~
If an edge cannot be created, report the URL and the missing edge rather than implying the tree is complete.
### 8. Read your own titles back
Before reporting, list every title with no other context — no body, no conversation, no repository open — and put each through both checks:
1. **The shape.** For a decision, point at the unresolved choice and its affected actor or surface. For a behavior title, point at the first half and the second half. For a code-internal title, point at the operation and the object. If you cannot point at the relevant parts, rewrite the title around the person, action, choice, operation, or checkable result.
2. **The output check.** For a decision, answer *what choice is unresolved and why now?* For a defect, answer *what is broken?* For a capability, answer *what should someone be able to do?* If the honest reaction is *ok, and?*, it fails, however many rules it obeys.
3. **The witness.** For a reported user-visible defect, the title names the interaction and observable result. For a change, fix, or capability issue, the title names the desired action and result in the verifier's language. A title its verifier cannot act on fails in either mode.
Then read every noun once more and ask whether the verifier knows it. A repo-specific noun the body never defines is a lookup you owe the reader — define it where it first appears, in prose.
Any title that fails gets rewritten and the issue edited before you report. This step is where the bad ones get caught — they always read fine while the reasoning is still in your head.
## Final report
Report the issue URLs, the labels that actually landed, the milestone each issue actually sits in (with the URL of any milestone you created), the sub-issue and blocked-by edges that actually exist, each replaced issue and how it was closed, any catch-all milestone you flagged, and any unresolved publication step. Name the gaps rather than implying completeness.