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.
This is the Claude-side counterpart to .codex/skills/gh-issues/SKILL.md — but 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 labelfoo.--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:
# Unassigned
gh issue list \
--state open \
--search "no:assignee" \
--json number,title,labels,assignees,createdAt \
--limit 200
# Assigned to me
gh issue list \
--state open \
--assignee "@me" \
--json number,title,labels,assignees,createdAt \
--limit 200
Merge:
- Deduplicate by
number. - Keep only issues whose
assigneesarray is empty or contains the current user (gh api user -q .login). - Drop issues assigned to anyone else (defensive).
- Apply
--labelfilter if given. - Apply
--limitif 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
$melisted → already mine, proceed (skip self-assign step). - Any other login present → skip this issue.
Invoke the
/gh-issueskill 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/) - TDD implementation
composer testgreen locally- changelog entry under
## Unreleased - commit with
Related to #<num> - mandatory final refactor pass over every touched file (separate
ref(...)commit) before opening the PR - PR opened via
/pr #<num>
- self-assign via
After
/gh-issuereturns, wait for CI green on the PR:gh pr checks --watchFix red checks on the branch before moving on.
Merge when allowed:
gh pr merge --auto --squash --adminSync
mainfor next iteration:git checkout main && git fetch origin main && git reset --hard origin/mainContinue with next issue.
Stop Conditions
Halt the loop and surface the failure when:
/gh-issueerrors out or leaves the worktree dirty.composer testfails after implementation.- CI stays red after one fix attempt.
- Merge is blocked by branch protection beyond
--adminbypass. --limitreached.- 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
ghauthenticated, can read issues, open and merge PRs.- Worktree clean.
mainexists and tracksorigin/main./gh-issueand/prskills available in this session.
Notes
- Commits inside
/gh-issueshould usegit commit --no-verify(project rule: 6 env-failing tests block the local pre-commit hook). - Treat GitHub CI as the full quality gate; locally run focused tests during implementation, full
composer testonce before commit. - Never split bundled changes into multiple PRs unless the issue explicitly demands it.