원클릭으로
github-issues
Create, search, triage, label, assign, comment on, and close GitHub issues using the gh CLI, with a curl REST fallback.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Create, search, triage, label, assign, comment on, and close GitHub issues using the gh CLI, with a curl REST fallback.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | github-issues |
| description | Create, search, triage, label, assign, comment on, and close GitHub issues using the gh CLI, with a curl REST fallback. |
| license | MIT |
| compatibility | Requires the `gh` CLI authenticated (or a GITHUB_TOKEN for the curl fallback) and a repo with a GitHub remote, or an explicit owner/repo. |
| allowed-tools | terminal_exec |
| metadata | {"gini":{"version":"1.0.0","author":"Gini","category":"dev","platforms":["macos","linux","windows"],"prerequisites":{"commands":["gh","git","curl"],"env":["GITHUB_TOKEN"]}}} |
Manage GitHub issues end to end: list, search, view, create, label,
assign, comment, close, reopen, triage, and run bulk operations. Every
section leads with the gh CLI (the preferred path) and follows with a
curl REST fallback for environments where gh is unavailable.
gh pr ….git log directly.gh is the preferred path. It must be installed and authenticated before
the first issue operation. Installing is non-interactive — do it
yourself (it's a side-effecting command, so it runs through the approval
seam; you propose it, you don't hand it to the user). Only the interactive
sign-in in step 2 needs the user.
Check for it, and if it's absent, detect the platform and run the matching install command yourself:
command -v gh >/dev/null 2>&1 && echo "gh present: $(gh --version | head -1)"
If that prints nothing, install it:
brew install gh # macOS / Linuxbrew
sudo dnf install -y gh # Fedora / RHEL
sudo pacman -S --noconfirm github-cli # Arch
winget install --id GitHub.cli # Windows
Debian/Ubuntu need the GitHub CLI apt repo added first — run this chain (don't ask the user to):
(type -p wget >/dev/null || (sudo apt update && sudo apt install -y wget)) \
&& sudo mkdir -p -m 755 /etc/apt/keyrings \
&& wget -nv -O- https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg >/dev/null \
&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
| sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null \
&& sudo apt update && sudo apt install -y gh
Only if the host genuinely can't install gh (no supported package
manager, no sudo) fall back to the curl/REST path in step 3.
gh auth status >/dev/null 2>&1 && echo "gh authenticated" || echo "gh NOT authenticated"
If it's not authenticated, tell the user in one line and stop — sign-in is interactive, so they run it, not you. Say something like:
Run
gh auth loginin your terminal to sign in, then I can file the issue.
Pick up where you left off once they confirm; re-run gh auth status to
verify before continuing.
# Prefer the gh CLI when it's present and signed in.
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
AUTH="gh"
else
AUTH="curl"
: "${GITHUB_TOKEN:?gh is unavailable — set GITHUB_TOKEN, or install and auth gh (steps 1-2)}"
fi
# Derive owner/repo from the origin remote (needed for the curl path).
OWNER_REPO=$(git remote get-url origin 2>/dev/null \
| sed -E 's|.*github\.com[:/]||; s|\.git$||')
OWNER=${OWNER_REPO%%/*}
REPO=${OWNER_REPO##*/}
gh reads $OWNER/$REPO from the remote automatically; pass
--repo owner/repo to any gh issue command to target a different repo.
For curl, the REST base is https://api.github.com/repos/$OWNER/$REPO.
gh:
gh issue list
gh issue list --state open --label "needs-triage" --limit 30
gh issue list --assignee @me --json number,title,labels
gh issue list --search "in:title timeout sort:created-desc"
gh issue view 128
gh issue view 128 --comments
curl:
# Open issues (the REST /issues endpoint also returns PRs — filter them out).
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues?state=open&per_page=30" \
| jq -r '.[] | select(.pull_request|not)
| "#\(.number) \(.state) \([.labels[].name]|join(",")) \(.title)"'
# A single issue with its metadata.
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues/128" \
| jq -r '"#\(.number): \(.title)\nState: \(.state) Labels: \([.labels[].name]|join(", "))\n\n\(.body)"'
# Full-text search scoped to the repo.
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/search/issues?q=timeout+in:title+repo:$OWNER/$REPO" \
| jq -r '.items[] | "#\(.number) \(.state) \(.title)"'
gh:
gh issue create \
--title "Webhook retries drop the Idempotency-Key header" \
--body "$(cat <<'EOF'
## Summary
Retried webhook deliveries are sent without the Idempotency-Key header,
so the downstream consumer treats each retry as a distinct event.
## Impact
Duplicate charges when a delivery is retried after a transient 5xx.
## Steps to Reproduce
1. Trigger a webhook whose first delivery returns 503.
2. Wait for the automatic retry.
3. Inspect the retried request headers.
## Expected
The retry carries the same Idempotency-Key as the original delivery.
EOF
)" \
--label "bug,webhooks" \
--assignee "@me"
curl:
curl -s -X POST -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues" \
-d "$(jq -nc \
--arg title "Webhook retries drop the Idempotency-Key header" \
--arg body $'## Summary\nRetried deliveries omit the Idempotency-Key header.\n\n## Impact\nDuplicate charges on retry.' \
'{title:$title, body:$body, labels:["bug","webhooks"], assignees:["octocat"]}')"
Two fillable templates ship alongside this skill:
templates/bug-report.md — Summary, Impact, Steps to Reproduce,
Expected vs Actual, Environment, Error Output, Additional Context.templates/feature-request.md — Problem, Who hits this, Proposed
direction, Alternatives considered, Out of scope, Additional Context.Fill one in, then pass it straight to gh instead of inlining a heredoc.
The templates sit next to this SKILL.md — under the repo at
skills/dev/github-issues/templates/ for the bundled skill, or under
~/.gini/instances/<inst>/skills/dev/github-issues/templates/ for a
user-installed copy.
gh issue create --title "…" --label "bug" \
--body-file skills/dev/github-issues/templates/bug-report.md
With curl, read the file into the body field: jq -nc --arg b "$(cat templates/bug-report.md)" '{title:"…", body:$b}'. Edit the placeholders
before submitting — don't file an issue with the <!-- … --> hints intact.
GitHub exposes no public attachment-upload API — gh issue, REST, and
GraphQL all lack an image upload, and the web drag-drop hits a private,
browser-only endpoint. To embed an image, commit it to a dedicated
issue-assets branch and reference its raw URL from the issue body.
Materialize the chat upload to disk. The upload id comes from the
user message marker (Attached image uploads (in order): - <id> (<mime>, <bytes> bytes)).
skill_run({skill: "attachments", script: "materialize", args: {uploadId: "<id-from-marker>"}})
→ {path, absPath}. Use absPath below so git reads the real file
regardless of the working directory.
Push the image to issue-assets with pure git plumbing — this never
touches the working tree, index, or current branch. git commit-tree
takes an explicit tree object; the parent it's given is history only and
does not contribute its tree. So to add an asset to the existing
branch you can't just mktree the new blob — you have to rebuild a tree
that contains both the prior assets and the new one. Do that in a throwaway
index (GIT_INDEX_FILE) so the real index stays untouched: seed it from
the prior tip's tree, add the new blob, then write the combined tree.
# Find the existing issue-assets tip on the remote — empty the first time,
# before the branch exists. Don't read FETCH_HEAD instead: a failed fetch
# leaves it pointing at an unrelated ref (e.g. the clone's default branch),
# which would fork that whole history into the asset branch.
PARENT=$(git ls-remote origin refs/heads/issue-assets | awk '{print $1}')
[ -n "$PARENT" ] && git fetch origin issue-assets # make that commit's objects local
BLOB=$(git hash-object -w "<absPath>")
# mktemp reserves the path; remove the empty file so git creates a fresh
# empty index there when there's no parent to read-tree (the first asset,
# before the issue-assets branch exists).
TMPIDX=$(mktemp); rm -f "$TMPIDX"
# Seed the temp index from the prior tip's tree so old assets are retained.
[ -n "$PARENT" ] && GIT_INDEX_FILE="$TMPIDX" git read-tree "$PARENT"
GIT_INDEX_FILE="$TMPIDX" git update-index --add --cacheinfo 100644 "$BLOB" "<filename>.png"
TREE=$(GIT_INDEX_FILE="$TMPIDX" git write-tree)
rm -f "$TMPIDX"
COMMIT=$(git commit-tree "$TREE" ${PARENT:+-p "$PARENT"} -m "Add issue asset: <desc>")
git push origin "$COMMIT":refs/heads/issue-assets
Verify the raw URL resolves, then reference it in the issue body:
curl -s -o /dev/null -w '%{http_code}\n' \
"https://raw.githubusercontent.com/$OWNER/$REPO/issue-assets/<filename>.png"
# expect 200
Body markdown: .
Create or comment as usual — gh issue create --body-file … /
gh issue comment … (Sections 2 and 3) with that markdown in the body.
gh:
gh issue edit 128 --add-label "priority:high" --add-label "webhooks"
gh issue edit 128 --remove-label "needs-triage"
gh label list # what labels exist on this repo
curl:
# Add labels.
curl -s -X POST -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues/128/labels" \
-d '{"labels":["priority:high","webhooks"]}'
# Remove one label.
curl -s -X DELETE -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues/128/labels/needs-triage"
# List the labels defined on the repo.
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/labels?per_page=100" \
| jq -r '.[] | "\(.name)\t\(.description // "")"'
Confirm a label exists (gh label list) before applying it — GitHub
returns a generic 422 for an unknown label rather than creating it.
gh:
gh issue edit 128 --add-assignee @me
gh issue edit 128 --add-assignee octocat --remove-assignee former-owner
curl:
curl -s -X POST -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues/128/assignees" \
-d '{"assignees":["octocat"]}'
gh:
gh issue comment 128 --body "Root cause is in the retry middleware — it rebuilds the request without copying idempotency headers. Fix in progress."
curl:
curl -s -X POST -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues/128/comments" \
-d "$(jq -nc --arg b "Root cause is in the retry middleware. Fix in progress." '{body:$b}')"
gh:
gh issue close 128 --reason completed --comment "Fixed in #131."
gh issue close 142 --reason "not planned"
gh issue reopen 128
curl:
# state_reason is one of: completed | not_planned | reopened
curl -s -X PATCH -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues/128" \
-d '{"state":"closed","state_reason":"completed"}'
curl -s -X PATCH -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues/128" \
-d '{"state":"open"}'
A PR that includes a closing keyword in its body auto-closes the issue
when it merges: Closes #128, Fixes #128, or Resolves #128.
Start a branch directly from an issue:
gh issue develop 128 --checkout # gh creates and checks out the branch
# manual equivalent:
git switch main && git pull && git switch -c fix/issue-128-idempotency-key
When asked to triage:
gh issue list --label "needs-triage" --state open.gh issue view <n>) to understand whether
it's a bug, a feature, or a question.needs-triage (see
Labels above).There is no batch endpoint — loop over issue numbers client-side.
gh:
# Close every issue carrying the "wontfix" label as not-planned.
gh issue list --label "wontfix" --state open --json number --jq '.[].number' \
| xargs -I {} gh issue close {} --reason "not planned"
curl:
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues?labels=wontfix&state=open&per_page=100" \
| jq -r '.[] | select(.pull_request|not) | .number' \
| while read -r n; do
curl -s -X PATCH -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/issues/$n" \
-d '{"state":"closed","state_reason":"not_planned"}' >/dev/null
echo "closed #$n"
done
| Action | gh | curl endpoint |
|---|---|---|
| List | gh issue list | GET /repos/{o}/{r}/issues |
| View | gh issue view N | GET /repos/{o}/{r}/issues/N |
| Search | gh issue list --search "…" | GET /search/issues?q=…+repo:{o}/{r} |
| Create | gh issue create … | POST /repos/{o}/{r}/issues |
| Add labels | gh issue edit N --add-label … | POST /repos/{o}/{r}/issues/N/labels |
| Assign | gh issue edit N --add-assignee … | POST /repos/{o}/{r}/issues/N/assignees |
| Comment | gh issue comment N --body … | POST /repos/{o}/{r}/issues/N/comments |
| Close | gh issue close N --reason … | PATCH /repos/{o}/{r}/issues/N |
| Reopen | gh issue reopen N | PATCH /repos/{o}/{r}/issues/N |
/issues collection also returns pull requests; filter with
select(.pull_request|not) (curl) — gh issue already excludes them./search/issues for a few seconds. List endpoints reflect it immediately.issue-assets
branch + raw URL (see "Attaching an image to an issue").gh when it is authenticated; only drop to curl when gh
cannot be installed or authenticated. If gh is missing, install it
yourself (Setup step 1) — don't ask the user to. Only the interactive
gh auth login (Setup step 2) goes to the user; don't try to drive
that sign-in headlessly.gh use its stored credential, or pass GITHUB_TOKEN via the
environment for curl.Closes #N) in PR bodies rather than manually
closing an issue that a merge will resolve.#128) so GitHub deeplinks
resolve them.Google Calendar via gws: list events, create, accept, find free time.
Google Docs via gws: read, append text, structured batch edits.
Google Drive via gws: search, list, upload, download, share.
Google Forms via gws: create forms, add items, read responses.
Gmail via gws: send, read, search, label, draft, reply, forward.
Google Sheets via gws: read/write cells, append rows, structured batch edits.