github-issues
Create, search, triage, label, assign, comment on, and close GitHub issues using the gh CLI, with a curl REST fallback.
git clone --depth 1 https://github.com/Open-Curiosity/gini-agent /tmp/github-issues && cp -r /tmp/github-issues/skills/dev/github-issues ~/.claude/skills/github-issuesSKILL.md
# GitHub Issues
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.
## When To Use
- User asks to file, view, search, or update a GitHub issue.
- User asks to triage a backlog ("label the untriaged issues", "assign
the open bugs to me").
- User asks for the status of issues on a repo ("what's open on
owner/repo", "show me the bugs I'm assigned").
## When NOT to Use
- Pull-request review or merge flows → not covered here; use `gh pr …`.
- Linear, Jira, or other trackers → use their dedicated skill.
- Plain local git history questions → answer with `git log` directly.
## Setup
`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.
### 1. Install gh if it's missing
Check for it, and if it's absent, detect the platform and run the matching
install command yourself:
```bash
command -v gh >/dev/null 2>&1 && echo "gh present: $(gh --version | head -1)"
```
If that prints nothing, install it:
```bash
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):
```bash
(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.
### 2. Authenticate gh if it isn't signed in
```bash
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 login` in 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.
### 3. Resolve the auth mode and target repo
```bash
# 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`.
## 1. Listing and Searching
**gh:**
```bash
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:**
```bash
# 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)"'
```
## 2. Creating Issues
**gh:**
```bash
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:**
```bash
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"]}')"
```
### Body templates
Two fillable templates ship alongside this skill:
- `tHow to dogfood and verify a Gini behavior change by driving a real chat turn as a real user would. Use when verifying that the agent reaches for a tool or path on its own — a behavioral steer, a new tool, an INSTRUCTIONS.md change, or a dispatch/provider/memory/skill change — or before claiming a steer "works". Enforces bare, uncoached prompts so the test measures the default, not instruction-following.
Delegate coding work to Claude Code CLI for repository edits, reviews, and multi-turn implementation sessions.
Delegate coding work to the OpenAI Codex CLI for repository changes, reviews, and focused fixes.
Gini's self-knowledge: how Gini configures, extends, and operates on its own state via /api/* and registered tools. Load when the user asks Gini about its own capabilities or asks Gini to modify its own configuration.
Manage Apple Notes via memo CLI: create, search, edit.
Apple Reminders via remindctl: add, list, complete.
Move bytes between Gini upload space, external URLs, and workspace files. Used by every attachment / file-upload / file-download flow regardless of the target system (Linear, GitHub, S3, Notion, etc.).
File a locally-captured, already-redacted Gini crash report as a GitHub issue, with the user's consent. Reads the pending crash queue and delegates the actual filing to the github-issues skill.