| name | gh-issues |
| description | Walk over all open GitHub issues that are unassigned or assigned to the current user, and process each one via the /gh-issue skill, sequentially. |
| user-invocable | true |
| argument-hint | [--limit N] [--label foo] [--dry-run] |
| disable-model-invocation | true |
| allowed-tools | Read, Bash, Skill |
GitHub Issues Watcher
Purpose
Process every open GitHub issue that is unassigned or assigned to the current user (@me), one after another, by delegating each to the /gh-issue skill. Stop on first hard failure so it can be inspected.
Driven from inside the Claude session rather than a polling shell script.
Args
--limit N — process at most N issues this run (default: all).
--label foo — only issues carrying label foo.
--dry-run — list issues that would be processed; do not invoke /gh-issue.
Strip leading # if user passes #123 style.
Phase 1: Discover
Fetch open issues that are unassigned or assigned to @me, oldest first. GitHub search does not OR these cleanly, so run two queries and merge:
gh issue list \
--state open \
--search "no:assignee" \
--json number,title,labels,assignees,createdAt \
--limit 200
gh issue list \
--state open \
--assignee "@me" \
--json number,title,labels,assignees,createdAt \
--limit 200
Merge:
- Deduplicate by
number.
- Keep only issues whose
assignees array is empty or contains the current user (gh api user -q .login).
- Drop issues assigned to anyone else (defensive).
- Skip tracker/epic issues — those whose body is a checklist of other issues (e.g.
- [ ] #123 …). Process the child issues directly, not the parent.
- Apply
--label filter if given.
- Apply
--limit if given.
- Sort ascending by
createdAt (FIFO).
Print the queue: #<num> <title> [assignee] per line, where [assignee] is unassigned or @me. If empty, exit cleanly.
Phase 2: Worktree Sanity
Before touching any issue:
git status --porcelain
git fetch origin main
git checkout main && git reset --hard origin/main
Abort if worktree dirty. Never auto-stash.
Phase 3: Process Loop
For each issue in the queue:
-
Re-check assignment state (someone else may have grabbed it):
gh issue view <num> --json assignees -q '.assignees[].login'
me=$(gh api user -q .login)
- Empty output → unassigned, proceed.
- Only
$me listed → already mine, proceed (skip self-assign step).
- Any other login present → skip this issue.
-
Invoke the /gh-issue skill with the issue number. That skill owns:
- self-assign via
gh issue edit <num> --add-assignee @me (no-op if already assigned)
- branch from fresh
main (prefix from labels: fix/, feat/, docs/)
- strict TDD: RED → GREEN → REFACTOR (no production code without a failing test first)
- full suite green locally (
./bashunit tests/ and ./bashunit --parallel tests/)
CHANGELOG.md entry under ## Unreleased for user-facing changes
- commit with conventional message +
Closes #<num> in the body
- PR opened via
/pr #<num>
-
Before pushing, run the exact command CI's strict job uses — plain
sequential runs miss simple-mode and strict-mode bugs that CI rejects:
./bashunit --parallel --simple --strict tests/
In --simple mode print_line emits only a one-char marker, so assert on
pure helper functions, not on print_line stdout. Under --strict
(set -euo pipefail): no [ cond ] && assignment (use if), initialise
arrays with =() and read elements as ${arr[i]:-}, and never enable
shopt -s extdebug in the parent shell (isolate it inside a $() subshell).
-
After /gh-issue returns, wait for CI green on the PR:
gh pr checks --watch
Fix red checks on the branch before moving on. Bash 3.0 CI fails often —
check that job specifically. A red docker/registry step is usually a
transient Docker Hub timeout, not your code: gh run rerun <id> --failed.
-
Merge when allowed:
gh pr merge --auto --squash --admin
-
Close the issue if the squash-merge did not. GitHub builds the squash commit from the PR body, and /pr writes Related #<num> there (never Closes), so the Closes #<num> in the branch commit body is lost. After merge:
gh issue view <num> --json state -q .state
gh issue close <num> --reason completed -c "Done in #<pr> (merged)."
If the issue belongs to a tracker/epic, tick its checkbox in the parent
issue body. Use sed for the tick — bash ${body/- [ ] #N/...} treats
[ ] as a glob (it matches a space, not literal brackets) and silently
does nothing:
gh issue view <tracker> --json body -q .body \
| sed 's/- \[ \] #<num>/- [x] #<num>/' | gh issue edit <tracker> --body-file -
-
Sync main for next iteration:
git checkout main && git fetch origin main && git reset --hard origin/main
-
Continue with next issue.
Stop Conditions
Halt the loop and surface the failure when:
/gh-issue errors out or leaves the worktree dirty.
./bashunit tests/ or ./bashunit --parallel tests/ fails after implementation (for the right reason — distinguish pre-existing failures; see Notes).
make sa (ShellCheck) or make lint (EditorConfig) fails.
- CI stays red after one fix attempt.
- Merge is blocked by branch protection beyond
--admin bypass.
--limit reached.
- Queue empty.
Do not retry blindly. Report which issue failed and why.
Dry Run
With --dry-run, only execute Phase 1 and print the queue. No assignment, no branching, no commits.
Preconditions
gh authenticated, can read issues, open and merge PRs.
- Worktree clean.
main exists and tracks origin/main.
/gh-issue and /pr skills available in this session.
Notes
- Bash 3.0+ compatibility is mandatory. No
declare -A, [[ ]], ${var,,}, negative array indexing, or &>> in src/. See .claude/rules/bash-style.md.
- Quality gate is
make sa + make lint, not bare shfmt. shfmt -w . without project flags rewrites the whole tree (collapses line-continuations, flips binary-op style, tabs the indent_size = unset files). Match the surrounding 2-space style by hand and rely on make lint (EditorConfig) to verify. There is no shfmt make target.
- Know your baseline.
tests/unit/coverage_subshell_test.sh fails on some macOS setups regardless of the change — confirm a failure is new (git stash + re-run) before treating it as a regression.
- Treat GitHub CI as the full quality gate; locally run focused tests during implementation, the full sequential + parallel suite once before commit.
- Never split bundled changes into multiple PRs unless the issue explicitly demands it.