| name | issue |
| description | Capture a problem or change request, verify it lightly against the codebase, draft a structured issue report, then route to one of: upload to GitHub/GitLab, document in code, hand off for implementation, or a free-text next step. Use when user says "/issue", "report a problem", "file a bug", "raise an issue", "track this", or "open a ticket". |
| allowed-tools | ["Bash","Read","Write","Edit","Glob","Grep","Task","AskUserQuestion"] |
Issue
Workflow
Follow all 6 steps sequentially.
Step 1: Parse Input + Preflight
-
Parse everything after /issue as RAW_DESC.
/issue <text> -- RAW_DESC=<text>
/issue (bare) -- print "What's the issue? Describe what happened and where (file/symbol if known)." and stop the turn. Treat the user's next message as RAW_DESC.
-
Run a single Bash call to gather context. Defer gh/glab auth checks until Step 6A -- the skill is useful for options 2/3/4 even without remote auth.
IS_GIT=$(git rev-parse --is-inside-work-tree 2>&1)
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo "")
CURRENT=$(git symbolic-ref --short HEAD 2>/dev/null || echo "DETACHED")
case "$REMOTE_URL" in
*github.com*) HOST="github" ;;
*gitlab.com*|*gitlab.*) HOST="gitlab" ;;
"") HOST="none" ;;
*) HOST="unknown" ;;
esac
GH_AVAILABLE=$(command -v gh >/dev/null 2>&1 && echo "yes" || echo "no")
GLAB_AVAILABLE=$(command -v glab >/dev/null 2>&1 && echo "yes" || echo "no")
ISSUE_ID=$(date +%s)
echo "IS_GIT=$IS_GIT"
echo "REPO_ROOT=$REPO_ROOT"
echo "REMOTE_URL=$REMOTE_URL"
echo "HOST=$HOST"
echo "CURRENT=$CURRENT"
echo "GH_AVAILABLE=$GH_AVAILABLE"
echo "GLAB_AVAILABLE=$GLAB_AVAILABLE"
echo "ISSUE_ID=$ISSUE_ID"
- Route based on the results:
| Condition | Action |
|---|
Not a git repo (IS_GIT is not true) | Report "Not a git repository. /issue needs a git repo to anchor the report." and STOP |
RAW_DESC empty after the bare-invocation prompt | Report "No description provided." and STOP |
RAW_DESC shorter than 8 chars or only generic words ("bug", "broken", "fix") | Print "That description is too vague. Add detail: what happened, where (file/symbol if known), what you expected." and stop the turn. Append the user's next message to RAW_DESC and re-validate. |
HOST in {none, unknown} | Continue. Step 5 option 1 (upload) will be omitted because no GitHub/GitLab remote was detected. |
Store all preflight vars for downstream steps.
Step 2: Contextualize and Verify (Light)
Extract references from RAW_DESC:
- Path-like tokens -- regex match on extensions:
\.(ts|tsx|js|jsx|py|rs|go|md|yaml|yml|json|sh|java|c|h|cpp|hpp|kt|swift|rb|php|sql)(:\d+)?
- Symbol-like tokens -- CamelCase or snake_case identifiers >=4 chars, skipping a stoplist (
bug, error, issue, fix, test, function, method, class)
- Quoted error strings -- text in backticks or single/double quotes, especially patterns like
TypeError: or Error:
Apply verification rules:
| Signal | Action |
|---|
| 0 refs extracted | Skip verification. Verdict is not-verifiable. Continue to Step 3. |
| 1-2 file paths that exist | Read each (cap at 200 lines, or a window around the :line if specified). Confirm the area matches the description. |
| 1-2 symbols, no paths | Single Grep per symbol. If a definition is found, Read 40 lines around it. |
| Referenced file does not exist | Flag in the draft as "referenced file not found" and continue. |
| >=3 distinct file paths, OR phrases like "across the codebase", "everywhere we", "find all", "audit" | Spawn one Task (subagent_type: general-purpose) with RAW_DESC + extracted refs. Prompt the subagent: "Verify the issue description against the codebase. Read referenced files, grep for symbols, identify whether the described problem is present. Return verdict, up to 5 file:line anchors, contradictions." |
Produce an EVIDENCE block:
- Verdict -- one of
confirmed | partial | cannot-confirm | not-verifiable
- Anchors -- up to 5
file:line bullets
Never block on cannot-confirm -- flag it in the draft and let the user decide.
Step 3: Draft the Report
Infer issue type from RAW_DESC heuristics:
| Type | Trigger phrases |
|---|
bug | "broken", "crash", "error", "doesn't work", "fails", "regression" |
enhancement | "should also", "add support for", "would be nice", "missing" |
proposal | "we should change", "refactor", "redesign", "consider migrating" |
question | "why does", "is it intentional", "should this" |
Default to enhancement if no signals match. Show the inferred type in the draft -- the user can correct it in Step 4.
Draft the report using this structure:
## Issue Draft
**Title:** {<=70 chars, capitalized, action-oriented, no conventional prefix}
**Type:** {bug | enhancement | proposal | question}
**Verification:** {confirmed | partial | cannot-confirm | not-verifiable}
**Suggested labels:** {comma-separated, <=4: one type label + optional area + optional priority}
---
### Summary
{1-2 sentences. What is the problem or change in one breath.}
### Context
- `path/to/file.ts:42` -- {what the code does today}
- `path/to/file.ts:88` -- {related call site}
### Reproduction _(bug only -- omit otherwise)_
1. {steps if known; else "Not provided -- needs reporter input"}
### Expected vs Actual _(bug only)_
- **Expected:** {...}
- **Actual:** {...}
### Suggested resolution
{1 paragraph or 2-4 bullets, framed as a proposal not a prescription. When verdict is `cannot-confirm`, lead with "Investigate whether..."}
### Open questions
- {ambiguities the reporter or maintainer needs to resolve}
Drafting rules:
- Derive content from
RAW_DESC + Step 2 evidence only. Do not fabricate reproduction steps, expected behavior, or evidence the user did not supply -- write "Not provided" instead.
- Total body under 60 lines.
- For
enhancement / proposal / question, omit the Reproduction and Expected/Actual sections entirely.
- Labels: at most 4. One type label (
bug/enhancement/proposal/question), one area label inferred from the top-most file path (area:auth, area:api), and at most one priority (p1/p2/p3) if signals like "crash", "data loss", or "blocker" appear in RAW_DESC.
Step 4: Confirmation Loop
Present the draft inline. Then AskUserQuestion with single-select options:
- Looks good -- proceed to Step 5.
- Edit -- after the picker resolves to this option, print "What would you like to change? (title, body section, labels)" and stop the turn. Apply the user's next message exactly as specified, do not re-derive. Re-present the updated draft and re-ask Step 4.
- Regenerate from scratch -- discard the draft and re-run Steps 2-3. After one regeneration, replace option 3 with "Already regenerated -- use this one anyway" to prevent infinite loops.
- Cancel -- discard and STOP with "Cancelled. No issue created."
Loop until the user picks option 1 or 4.
Step 5: Choose Next Step
AskUserQuestion, single-select:
- Upload to issues -- push to GitHub/GitLab via
gh / glab. Omit this option entirely when HOST is none or unknown (do not present it as selectable).
- Document in code -- add a
TODO(issue): comment near the relevant code, append to ISSUES.md, or write to a custom path.
- Implement a fix now -- hand off the report as context so the main agent (or a Task subagent) starts work.
- Something else -- free-text field for edits, multi-action sequences, custom paths.
Step 6: Execute the Chosen Action
Before any sub-flow, use the Write tool to create /tmp/issue-${ISSUE_ID}.md with the final body. Do not use echo, cat <<EOF, or shell redirects.
6A. Upload to issues
-
Just-in-time auth check (deferred from Step 1):
gh auth status 2>&1; echo "EXIT=$?"
glab auth status 2>&1; echo "EXIT=$?"
If EXIT != 0, report:
"GitHub CLI not authenticated. Run gh auth login, then re-run /issue. Your draft is saved at /tmp/issue-${ISSUE_ID}.md."
(Substitute glab auth login for GitLab.) Preserve the temp file in this case and STOP.
-
CLI missing (GH_AVAILABLE=no or GLAB_AVAILABLE=no): report brew install gh / brew install glab, preserve the temp file, and STOP. If the user mentioned gl, explicitly note: "GitLab CLI is glab, not gl."
-
Duplicate detection (GitHub only, v1):
gh issue list --search "{first 6 significant title words} in:title" --state open --json number,title,url --limit 5
If >=1 result, AskUserQuestion with options:
- Create anyway -- proceed
- View [#N] -- print URL and STOP
- Comment on existing [#N] --
gh issue comment N --body-file /tmp/issue-${ISSUE_ID}.md, then clean up temp file
- Cancel -- discard and STOP
GitLab duplicate detection is deferred -- see Guidelines.
-
Label filtering: before passing --label, run once:
gh label list --json name --jq '.[].name'
Intersect with suggested labels, drop unknowns. Report which were dropped in the success block. (Unknown labels make gh issue create fail with an opaque error.) For GitLab, run glab label list instead.
-
Create the issue:
For GitHub:
gh issue create \
--title "{title}" \
--body-file /tmp/issue-${ISSUE_ID}.md \
--label "{filtered}"
For GitLab:
glab issue create \
--title "{title}" \
--description-file /tmp/issue-${ISSUE_ID}.md \
--label "{filtered}"
-
Capture the URL from stdout. Report:
## Issue Filed
**URL:** {url}
**Title:** {title}
**Host:** {github | gitlab}
**Labels applied:** {final labels}
**Labels dropped:** {dropped labels, if any}
-
Clean up: rm -f /tmp/issue-${ISSUE_ID}.md.
6B. Document in code
AskUserQuestion with single-select placement options:
- Inline at
{file:line} from Step 2 evidence -- only show when Step 2 produced a confirmed verdict with a single dominant file:line anchor.
- Append to
ISSUES.md at repo root -- create if missing. Recommended default.
- Custom path -- after the picker resolves to this option, print "Enter the target path (default:
docs/issues/{slug-of-title}.md)." and stop the turn. Treat the user's next message as the path.
Inline comment (language-aware by file extension):
| Extensions | Comment prefix |
|---|
.ts, .tsx, .js, .jsx, .go, .rs, .java, .c, .h, .cpp, .hpp, .kt, .swift, .php | // |
.py, .sh, .yml, .yaml, .rb | # |
.sql, .hs | -- |
.md, .html | <!-- / --> |
Template:
{prefix} TODO(issue): {Title}
{prefix} {1-line suggested resolution}
For HTML/Markdown, wrap as <!-- TODO(issue): {Title}\n{1-line suggested resolution} -->.
Insert via Edit: old_string = the original line, new_string = {comment block}\n{original line}. If Edit fails on non-uniqueness, expand old_string with more surrounding context and retry once. On second failure, fall back to ISSUES.md.
ISSUES.md (idempotent): Read the file first if it exists. Append under ## Open (create the section if missing). Section template:
### {Title}
**Filed:** {YYYY-MM-DD} | **Type:** {type} | **Labels:** {labels}
{Summary section from the draft}
**Evidence:**
- `file:line` -- {detail}
**Suggested resolution:** {one paragraph from draft}
---
If the file does not exist, create it with this header:
# Issues
Locally tracked issues. Promote to GitHub/GitLab when ready.
## Open
{first entry}
Custom path: use Write to create the file with the full draft as the body.
After success, clean up /tmp/issue-${ISSUE_ID}.md and report:
## Documented
**Location:** {file:line | ISSUES.md | custom path}
**Next:** Commit when ready.
6C. Implement a fix (handoff)
This skill does not implement the fix. Default to inline handoff so the parent agent continues with full conversation context.
Escalate to a Task subagent only when both: (a) Step 2 used a Task agent (>=3 files referenced), and (b) the suggested resolution touches >=2 of those files. Otherwise use inline handoff.
Inline handoff output:
## Now implementing: {Title}
Report saved at /tmp/issue-${ISSUE_ID}.md.
### Plan
1. {first action item from suggested resolution}
2. {second action item}
3. {...}
### Files involved
- `path/to/file1.ts` ({why})
- `path/to/file2.ts` ({why})
Starting on item 1.
The next turn (from the parent agent) picks up from "Starting on item 1." Skill stops after emitting the handoff. Never auto-commit -- the user runs /commit after reviewing.
Task subagent path (complex only):
Use Task (subagent_type: general-purpose) with this prompt structure:
Implement the fix described in /tmp/issue-${ISSUE_ID}.md.
## Files involved
{list}
## Plan
{numbered list from the resolution}
## Constraints
- Make the minimal change needed.
- Do not modify unrelated code.
- Do not commit. The user will review and commit themselves.
- Report each file you edit with a one-line summary.
When done, summarize what changed and any tests you would recommend running.
Report the subagent's summary back to the user. Do not loop into another round of fixes -- the user takes over.
6D. Something else (free text)
Print "What would you like to do? Examples: 'edit the report', 'upload and also document', 'save to ~/notes/issue.md', 'cancel'." and stop the turn. Interpret the user's next message and route:
| Pattern | Action |
|---|
| "edit", "revise", "change the {section}" | Re-enter Step 4 with the user's edits applied |
| "upload AND document", "do both", "1 and 2", "all" | Run 6A then 6B then 6C in the order implied. Between each sub-flow, AskUserQuestion with options Continue to {next} / Stop here. |
| "save to {path}" | Use Write to create {path} with the full draft body, report success per 6B's template. Do not re-prompt for the path. |
| "cancel", "abandon", "never mind" | Discard /tmp/issue-${ISSUE_ID}.md and STOP |
| Other interpretable intent | State the interpretation back, then AskUserQuestion with options Confirm / Re-explain / Cancel. Execute on Confirm; re-enter 6D on Re-explain; STOP on Cancel. |
| Uninterpretable | AskUserQuestion with options Explain differently / Go back to the 4 options / Cancel. |
After any success path, clean up /tmp/issue-${ISSUE_ID}.md.
Guidelines
- Always render the picker for enumerated options. Whenever a step lists 2-4 selectable choices, you MUST invoke
AskUserQuestion so the user sees a pre-filled picker. Never print numbered options in markdown alone and wait -- the user cannot select from inline text. For genuine free-text inputs (no enumerable choices), print the question and stop the turn so the user replies in chat; do not call AskUserQuestion with invented options.
- GitLab CLI is
glab, not gl. Every user-facing message must use glab. If the user mentioned gl anywhere, gently correct them in the error/info copy.
- Never auto-commit. After any sub-flow, the user runs
/commit themselves. Do not stage or commit on their behalf.
- No
--dry-run flag. Step 4 confirmation and Step 5 picker are the safe abort points. /tmp/issue-${ISSUE_ID}.md is the dry-run artifact.
- Sub-agents are an escalation, not a default. Step 2 stays inline unless the >=3-file trigger fires; Step 6C stays inline unless Step 2 already escalated AND >=2 files need editing.
- Self-hosted hosts (Gitea, Bitbucket, self-hosted GitLab on custom domains) classify as
unknown; users route through option 4. No env-var override in v1.
- Title quoting. When passing the title to
gh issue create / glab issue create, ensure it is properly quoted to handle special characters.