file-github-issue
Use when a user wants to file a GitHub issue with a screenshot against a GitHub repo. Expected inputs: a screenshot, a short description, the target repo, and the page URL or app section where the issue was observed (the skill prompts for the URL if missing).
- License 许可
- Apache-2.0
- 最近更新
- 2026-11-12
- Stars
- ★ 0
- Forks
- 1
01 前言 Front Matter
- name
- file-github-issue
- requires
- [ { "cli": "gh", "reason": "GitHub CLI for issue creation" } ]
- description
- Use when a user wants to file a GitHub issue with a screenshot against a GitHub repo. Expected inputs: a screenshot, a short description, the target repo, and the page URL or app section where the issue was observed (the skill prompts for the URL if missing).
- argument-hint
- [repo]
- user-invocable
- true
02 资源文件 1
- 参考文档body-template.md3.1 KB
03 SKILL.md 正文
File an issue
Codex: Read
../_shared/codex-tools.mdbefore following this skill. Apply its tool, root and dispatch mappings throughout.
Turn a screenshot + a short description into a well-structured GitHub issue on the target repo. Images are committed to a user-configured public attachments repo (set via the CLAUDNA_ATTACHMENTS_REPO=<owner>/<repo> env var) and embedded in the issue body via their raw URLs. If no attachments repo is configured, the skill prompts once and falls back to embedding the screenshot's local file path in the issue body.
Reference files:
body-template.md— issue body structure, title rules, proposed-label logic.
Procedure
Follow these steps exactly in order. If any prerequisite fails, stop and surface the error — do not improvise around it.
Step 1: Confirm intent and collect inputs
Trigger: the user invoked /claudna:file-github-issue [repo], or wrote ambient prose containing at least one image AND a filing-intent phrase ("open an issue", "report this", "log a bug", "file a bug", "file an issue"). If neither the image nor a filing phrase is present, ask the user to confirm intent and halt.
Collect:
- Image paths: every image attached to the current turn, in attachment order. If none, ask the user to attach a screenshot and halt this round.
- User prose: the free-form description accompanying the images.
- Target repo: resolution order — (1) slash-command arg if provided; (2) explicit
owner/reposlug in the prose; (3) current repo'soriginremote, derived viagit remote get-url originand parsed toowner/repo; (4) ask the user "Which repo?". If both an arg and a prose slug are provided, the arg wins.
Step 2: Validate and resolve the target repo
- Target repo: accept a fully-qualified
owner/reposlug. If a bare name is given (e.g.,my-app) and Step 1's resolution fell through to it, ask the user for the owner — do not guess. Record the resolved slug; every subsequent step uses it. Existence and access are verified implicitly by the label pre-flight (step 3) and the finalgh issue create— no separate allowlist. - Image format for each path: extension must be
.png,.jpg,.jpeg,.gif, or.webp(case-insensitive). Reject any other extension with a clear error naming the rejected path and the accepted set, then halt. - Tooling prereqs:
ghmust be installed and authenticated. Ifgh --versionfails: error withbrew install ghhint. Ifgh auth statusfails: error withgh auth loginhint.
Step 3: Label pre-flight
Run once the target repo is known, BEFORE rendering the preview (and again if a later edit changes the target repo):
gh label list --repo <repo-slug> --json name --jq '.[].name'
Intersect the command output with the proposed label set (see body-template.md) to get the effective label set. Record labels that were proposed but not present in the repo as dropped — they'll be shown inline in the preview and repeated as a post-filing warning.
If the pre-flight command itself fails (network, rate limit, non-zero exit): do not abort. Fall back to sending all proposed labels unchanged and note in the preview: "(label pre-flight unavailable; all proposed labels will be attempted)".
Step 4: Gather context and draft the issue
Lean toward richer context. The issue body is only as useful as what you pull in here. Don't fabricate — if a field has no source, prompt the user (for the Page/URL anchor, see 4b.5) or omit it (for optional enrichers like Action, Inputs, Links).
4a. Extract from the screenshot(s). Read each image carefully and pull:
- Page / URL: The browser URL bar (if visible in chrome), the window/tab title, in-app breadcrumbs, section headers. A screenshot of "MyApp — conversation — Item123" is a concrete anchor even without a full URL. If none of these are visible, leave the anchor blank — Step 4b.5 will ask the user directly.
- Visible UI state: error banners, toast messages, tool output text (including any "fallback" or "not supported" notes — quote them verbatim in
## What I saw). - Inputs: the exact query, prompt, entity, or selection the user used, if it appears in the screenshot.
4b. Extract from the user prose. Pull:
- Links to Slack threads, docs pages, dashboards, pipeline/CI runs, related PRs — any URL-shaped string.
- Explicit issue references (
#123,owner/repo#45). - Product/feature names that help disambiguate the surface.
4b.5. Prompt for URL if missing. Decide whether 4a + 4b produced a concrete Page/URL anchor. Concrete means: a browser URL visible in the screenshot chrome, a URL-shaped string in the prose, or an app + section + specific surface in the screenshot (e.g., "MyApp — entity page — Item123"). Just an app name ("MyApp") or an app + generic surface ("MyApp — dashboard") is NOT enough on its own. If nothing concrete, halt and ask:
I don't see a URL or page anchor in the screenshot or your description. What page is this from? Paste the URL or describe the surface — e.g.,
app.example.com/dashboardor "Settings → Notifications".
Wait for a reply. Use it as the Page/URL value.
- If the user replies with a URL or page description → use it verbatim.
- If the user replies "skip" / "unknown" / "n/a" → mark the body field as
unknown — user declined to provide. This makes the informed choice explicit rather than making it look like the skill forgot to ask. - Don't badger. One prompt, one reply, move on.
4c. Dupe and related-issue search. Run once, against the target repo (re-run if target repo changes during Step 5 edits). Use 2–3 salient keywords from the user prose and screenshot (the draft title doesn't exist yet — it's produced in 4d):
Write the keywords to <keywords-file>, a file in <scratch> (../_shared/orchestration-guide.md §1), with the Write tool, then:
gh issue list --repo <repo-slug> --search="$(cat <keywords-file>)" --state all --limit 10 --json number,title,state
Include any clearly related hits in the ## Related > Issues bullet with their state and a one-line relationship note. If the search returns nothing germane, the bullet is "none found" (or omit if there are also no Links).
If there's an obviously open duplicate with the same framing, do NOT silently file a second one — raise it in the preview and let the user choose (comment on the existing issue vs. file anyway with a cross-reference).
4d. Draft. Produce title, body, and proposed labels per body-template.md. Pick the single-image template if exactly one image was attached, otherwise the multi-image template. Leave <attachment-url-N> placeholders literal — they're substituted in step 6.
4e. Resolve the attachments repo. Resolution order:
CLAUDNA_ATTACHMENTS_REPOenv var (format:owner/repo). Validate that it parses cleanly — if malformed, surface the value and ask.- If unset, ask once: "This skill embeds screenshots by committing them to a public GitHub repo and linking the raw URL. Reply with
<owner>/<repo>to use one for this filing (and consider exportingCLAUDNA_ATTACHMENTS_REPO=<owner>/<repo>to make it persistent), or replyskipto file the issue with the screenshot's local file path embedded instead (won't render inline)."- If the user replies with a slug → record it in
$ATTACHMENTS_REPOfor this filing only. - If the user replies
skip→ set$ATTACHMENTS_MODE=localand skip the upload step. The preview and final body will usemarkdown.
- If the user replies with a slug → record it in
- The repo must already exist and the user must have write access. The skill never creates the attachments repo.
Step 5: Preview and confirm
Render exactly this block. The first line varies based on Step 4e resolution: if $ATTACHMENTS_REPO is set, use the "committed to <slug>" wording; if $ATTACHMENTS_MODE=local, use this instead: "Screenshot(s) will be embedded as local file paths only — they will not render inline. Filing only — no upload."
Screenshot(s) will be committed to <$ATTACHMENTS_REPO> (public repo; unlisted-by-path). Anyone with the URL can view. Cancel now if redaction is needed.
------------------------------------------
Target repo: <repo-slug>
Title: <title>
Labels: <effective set> (dropped: <comma-separated>, if any)
Body:
<full rendered markdown; each <attachment-url-N> placeholder shown as "(uploaded on confirm)" — for single-image render as "", for multi as "", etc.>
------------------------------------------
File this? [y / n / or describe edits]
Keep the <attachment-url-N> tokens in the internal body string — only the preview hides them behind the stub. The real URLs are swapped in after y (step 6).
Wait for the user's reply:
y→ proceed to step 6.norcancel→ exit cleanly. No commit, no issue.- Anything else → treat as edit instructions. Apply them (may change title, body, labels, or target repo). If the target repo changed, re-run step 3 (label pre-flight) AND step 4c (dupe search), since both are repo-scoped. Then re-render this preview. No round limit.
- Edit rounds can change text and metadata but NOT the image set. If the user wants to add, remove, or replace screenshots, treat that as a new
/claudna:file-github-issueinvocation — cancel this one and restart.
Step 6: Upload images
Only run after the user replies y. If $ATTACHMENTS_MODE=local from Step 4e, skip this entire step — substitute each <attachment-url-N> placeholder with file://<absolute-path> of the corresponding image instead, then go to step 7.
Images are committed to $ATTACHMENTS_REPO (resolved in Step 4e) on the main branch at <YYYY>/<MM>/<uuid>.<ext>. Each image gets a fresh UUID so paths don't collide and aren't guessable by enumeration.
For each image (in turn order):
- Compute the path:
EXT="${IMG_PATH##*.}" # e.g. "png" UUID=$(uuidgen | tr '[:upper:]' '[:lower:]') ATTACH_PATH="$(date +%Y)/$(date +%m)/$UUID.$EXT" - Commit the image via the Contents API. Base64-encode to a tmpfile first, then use
jq --rawfile— passing the base64 as a--argvalue still puts it on the command line and blows pastARG_MAXon anything over ~100KB:B64_FILE=$(mktemp "${TMPDIR:-/tmp}/file-github-issue-b64.XXXXXX") base64 -i "$IMG_PATH" | tr -d '\n' > "$B64_FILE" jq -n --arg msg "file-github-issue attachment: <title>" \ --rawfile content "$B64_FILE" \ '{message: $msg, content: $content}' \ | gh api --method PUT \ "/repos/$ATTACHMENTS_REPO/contents/$ATTACH_PATH" \ --input - rm "$B64_FILE" - Construct the raw URL:
ATTACH_URL="https://raw.githubusercontent.com/$ATTACHMENTS_REPO/main/$ATTACH_PATH" - Substitute the
<attachment-url-N>placeholder matching this image's turn position (image 1 →<attachment-url-1>, image 2 →<attachment-url-2>, etc.) with this image's$ATTACH_URL.
If any upload fails: abort before filing. Print the attachment URLs of any images already committed — they're persistent and re-usable. No rollback.
Max file size: the Contents API hard-limits at 100MB. For files over ~1MB the API also warns — still works for typical screenshots (well under), but if you hit a size error, surface it and halt.
Step 7: File the issue
- Write the substituted body to a tmpfile. BSD
mktempon macOS requiresXXXXXXat the end of the template — no.mdsuffix;gh --body-filedoesn't care about extension:
Write the title intoTMPFILE=$(mktemp "${TMPDIR:-/tmp}/claudna:file-github-issue.XXXXXX") TITLEFILE=$(mktemp "${TMPDIR:-/tmp}/claudna:file-github-issue.XXXXXX") LABELFILE=$(mktemp "${TMPDIR:-/tmp}/claudna:file-github-issue.XXXXXX")$TITLEFILEthe same way, and the effective labels, comma-separated, into$LABELFILE. The title and labels never go into the command itself. - File the issue with the effective label set from step 3:
Leave out thegh issue create \ --repo <repo-slug> \ --title "$(cat "$TITLEFILE")" \ --body-file "$TMPFILE" \ --label "$(cat "$LABELFILE")"--labelline when the effective label set is empty. - Delete the tmpfiles:
rm "$TMPFILE" "$TITLEFILE" "$LABELFILE". - Print the issue URL (from
ghstdout) and the attachment URL(s). If any labels were dropped in step 3, repeat the warning:⚠ Filed without labels (target repo missing:
<comma-separated>). Create them in the repo to enable labeling next time.
If gh issue create fails (e.g., 403/404 for missing permissions): surface the exact gh stderr and suggest requesting access. The attachment URLs are preserved — print them so the user can reuse or delete (delete via gh api --method DELETE /repos/$ATTACHMENTS_REPO/contents/<path> with the file's blob SHA).
Invocation
Two surfaces, same body underneath:
- Slash command:
/claudna:file-github-issue [repo]. Arg optional — if missing, default to the current repo'soriginremote (parsed fromgit remote get-url origin); if no remote or ambiguous, ask. Accepts a fully-qualifiedowner/reposlug. - Ambient: a user prompt with an image and filing-intent language ("open an issue…", "report this bug…"). Strong signals for activation: at least one image in the turn and a filing-intent phrase. These are guidance, not hard gates — step 1 re-checks for both and asks for confirmation if either is missing.
Failure modes
| Failure | Behavior |
|---|---|
| No image in the turn | Ask for one. Do not proceed. |
| Unsupported image format (not png/jpg/jpeg/gif/webp) | Error listing the rejected path and the accepted formats. Halt. |
| Image file unreadable / corrupt / empty | Error with the path. Ask to re-provide. Halt. |
gh not installed |
Error with brew install gh hint. Exit. |
gh auth status fails |
Error with gh auth login hint. Exit. |
| Target repo doesn't exist (404 at label pre-flight or issue create) | Surface gh stderr (404). Re-prompt the user for the correct repo. |
| User lacks issue:write on target repo | Surface gh stderr, suggest requesting access. Attachment URLs preserved — print them. |
User lacks write on the configured CLAUDNA_ATTACHMENTS_REPO |
Surface gh stderr, suggest requesting access or setting a different repo. Halt before filing. |
CLAUDNA_ATTACHMENTS_REPO unset and user replies skip |
File the issue with  markdown — won't render inline but preserves the path. |
| Body mentions a repo different from target | Warn inline in the preview; do not block. User is authoritative. |
| Contents API commit fails | Abort before gh issue create. Print error. |
| Image exceeds 100MB Contents API limit | Surface the error. Suggest compressing or using a smaller screenshot. Halt. |
| Issue creation fails after attachment upload | Print the orphaned attachment URLs. They're reusable if refiled. |
| Proposed label missing in repo | Caught by pre-flight (step 3). Dropped labels shown in preview and repeated post-filing. |
gh label list pre-flight fails |
Don't abort. Send all proposed labels. Note in preview. |
| User cancels at preview | Exit cleanly. No commit, no issue. |
| User interrupts mid-upload | No partial-cleanup guarantee. Already-committed images remain in the attachments repo. |
| Multi-image, any one upload fails | Abort all. Print already-committed attachment URLs so they can be reused or deleted. |
Notes
- Why
--body-file: avoids shell-escaping pain with markdown (backticks, newlines, special chars). - Why pre-flight labels (not retry-on-fail): if
gh issue createfails on a missing label, stripping all--labelflags would also drop the valid ones. Pre-flight gives a clean, one-round answer. - Why a public attachments repo (vs. gists):
gh gist createonly accepts text files — binaries like PNGs are rejected. GitHub has no public API for inline issue attachments (the web UI's paste-image flow uses a private session-cookie endpoint). Committing to a public repo and embedding the raw URL is the onlygh-reachable path that renders inline. Public is required because raw URLs from private repos go through signed, short-lived redirects that don't render in issue markdown. Secrecy is by-path: each image gets a fresh UUID, so the URL isn't guessable. - Why
jq --rawfile(not--argor-f content=…): base64-encoded images blow pastARG_MAXon anything over ~100KB. Bothjq --arg content "$(base64 ...)"andgh api -f content=…shell-expand the value onto the command line and fail with "argument list too long".--rawfilereads from a file descriptor, so the payload never touches argv. - Finding past attachments: browse
https://github.com/$ATTACHMENTS_REPO/tree/main/<YYYY>/<MM>/orgh api /repos/$ATTACHMENTS_REPO/git/trees/main?recursive=1 --jq '.tree[].path'.
Red Flags — STOP
If you catch yourself thinking any of these, STOP — you are about to file an issue that will land badly:
- "I'll just file it, skip the preview" — Preview exists because GitHub issues are team-visible and hard to retract. Read the draft before
y. - "Close enough on the repo" — Wrong repo = noise in someone else's tracker + real bug falls on the floor. If unsure, ask the user; don't guess.
- "The screenshot is fine to upload" — Screenshots routinely show customer names, credentials, internal URLs, dashboards. Pre-upload check: is there anything in this image you wouldn't paste in a public Slack channel? If yes, cancel and redact.
- "The description is sparse but good enough" — A two-word description makes the issue unactionable. The sparse-prose warning exists for a reason. Ask the user to add context via edit before filing.
- "Looks like a duplicate, but I'll file it anyway" — Step 4c runs the dupe search for you and surfaces candidates in the
Related > Issuesbullet. Still your job to read them and decide: if an open issue already covers this exactly, raise it in the preview and let the user choose (comment vs. file with cross-reference). Never silently file a second copy. - "Let me just create the repo / labels myself" — The skill is intentionally limited to filing issues. Creating repos or labels bypasses human review. Tell the user to request it through normal channels.
Common Rationalizations
| Excuse | Reality |
|---|---|
| "I can tell from the screenshot which repo" | Maybe, maybe not. The user is authoritative. Ask if unsure. |
| "The sparse warning is ugly, I'll drop it" | The warning is the feature — it nudges the user to add context before filing. |
| "I'll guess the URL from the app name" | Don't. Step 4b.5 prompts the user explicitly when extraction fails. Use their reply. Fabricated URLs are worse than missing ones. |
| "I'll skip the 4b.5 prompt, they can fix it in the preview" | No. The preview is for reviewing the draft, not for remembering missing anchors. Asking once upfront is cheaper than the user catching an "unknown" placeholder in the preview and re-triggering an edit round. |
| "The Related section is empty, I'll invent a tangentially-related issue" | No. If the dupe search found nothing and there are no links in the prose, omit the section entirely per body-template.md. |
| "I'll swap in the real attachment URLs now to save a step" | No. Commit happens only after y. A cancelled preview should never leave an attachment in the repo. |
| "Missing label? I'll create it" | Out of scope. Ship the issue without the label; flag the gap in the post-filing warning. |
"I'll just use cat <<EOF | gh issue create --body -" |
Backticks and pipes in markdown break this. Always --body-file. |
| "The pre-flight failed, I'll bail" | Don't. Fall back to sending all proposed labels and note it in the preview. |
| "The user's 4b.5 reply is terse / unhelpful, I'll reword or expand it" | No. One prompt, one reply. Use the reply verbatim. If it's wrong, the user will fix it in the preview edit round. |
| "The user changed the target repo in preview edits, the old dupe results probably still apply" | No. Dupe search is repo-scoped. Re-run step 4c alongside step 3 whenever the target repo changes. |
| "The user wants to swap the screenshot, I'll just attach it in the preview edit" | No. Edit rounds change text and metadata only. For image changes, cancel and restart /claudna:file-github-issue. |
04 同仓库更多 Skills
example-review
Use when you want to review pull requests with detailed, actionable feedback on code quality, correctness, and maintainability.
bad-skill
Too short
worktree
Use when starting feature work that needs isolation from the current workspace, or when running multiple Claude sessions in parallel on different branches — creates and manages git worktrees. For fetching latest main into every existing worktree and branch across your sibling repos, use /claudna:sync-branches instead.
weigh-development-paths
Use when at a development junction with multiple viable approaches — architecture choices, refactoring strategies, where to put new code, or which pattern to follow. Triggers on 'which approach', 'how should I', 'Option A vs B', or any point where the next step isn't obvious and the wrong choice creates rework.
verify-completion
Use when about to claim work is complete, fixed, or passing -- requires running verification commands and confirming output before making any success claims.
vercel
Use for Vercel operations — deploy to production or preview (or roll back), view, filter, or debug deployment logs, or check project status (deployments, domains, env vars, config). Replaces /vercel-deploy, /vercel-logs, /vercel-status.