- Published on
Automating Development with GitHub Agent Loops
- Authors

- Name
- Avasdream
- @avasdream_
What if your GitHub issues could resolve themselves? This guide shows how to build an agent loop that continuously processes issues labeled agent-ready, implements them using TDD, performs QA with real fixes, and closes them—all without human intervention.
I've been running this pattern across multiple projects (video generation, helpdesk apps, experiment platforms) and it's remarkably effective for well-defined tasks.
How It Works
┌─────────────────────────────────────────────────────────────┐
│ Agent Loop Cycle │
├─────────────────────────────────────────────────────────────┤
│ 1. Fetch oldest issue with 'agent-ready' label │
│ 2. Move to 'in-progress' │
│ 3. Implementation Phase (TDD with Agent Teams) │
│ 4. QA Phase (test, fix, verify) │
│ 5. Documentation Update (CLAUDE.md) │
│ 6. Close issue + push changes │
│ 7. Repeat │
└─────────────────────────────────────────────────────────────┘
The loop runs indefinitely (or for N iterations), picking up issues in order and processing them through a structured workflow. Each step is documented as a comment on the issue, providing full visibility into what the agent did.
Key Features
Label-Based Workflow
| Label | Meaning |
|---|---|
agent-ready | Issue is ready to be picked up |
in-progress | Agent is currently working on it |
completed | Successfully finished |
Issues move through these states automatically. If the script crashes, it resets any in-progress issues back to agent-ready on startup—crash recovery built in.
Structured Execution Steps
Each issue goes through seven documented steps:
- Starting Work – Agent announces pickup
- Implementation – TDD with Agent Teams (up to 10 parallel agents)
- Summary – Documents what was built
- QA Review & Fix – Runs tests, fixes any failures
- QA Report – Final status with diffs
- CLAUDE.md Update – Captures learnings
- Complete – Closes issue, pushes code
Agent Teams Integration
The script enables Claude Code's experimental Agent Teams feature:
export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
This allows up to 10 parallel agents working on different aspects:
- Writing tests in parallel
- Implementing separate modules simultaneously
- Running checks while others code
- Fixing different categories of issues concurrently
Setup Requirements
Prerequisites
# GitHub CLI authenticated
gh auth status
# Claude Code installed
claude --version
# Node.js project with these scripts
npm run build
npm run test
npm run typecheck
npm run lint
Directory Structure
your-project/
├── scripts/
│ └── loop-github.sh
├── logs/
│ └── agent-loop.log
├── CLAUDE.md
└── ...
GitHub Labels
Create these labels in your repository:
gh label create "agent-ready" --color "0E8A16" --description "Ready for agent"
gh label create "in-progress" --color "D93F0B" --description "Agent working"
gh label create "completed" --color "5319E7" --description "Agent finished"
Usage
Basic Run
# Run 10 iterations
./scripts/loop-github.sh 10
# Run forever
./scripts/loop-github.sh -1
Background Execution
# Run in background with logging
nohup ./scripts/loop-github.sh -1 > logs/agent-loop.log 2>&1 &
# Or use tmux for easy monitoring
tmux new-session -d -s agent-loop './scripts/loop-github.sh -1'
tmux attach -t agent-loop
Monitoring
# Watch the log
tail -f logs/agent-loop.log
# Check tmux session
tmux capture-pane -t agent-loop -p | tail -20
# List issues in progress
gh issue list --label "in-progress"
Creating Agent-Ready Issues
The quality of your issues directly impacts agent success. Structure them like this:
## Objective
Implement user authentication with JWT tokens.
## Requirements
- [ ] Login endpoint at POST /api/auth/login
- [ ] Token generation with 24h expiry
- [ ] Middleware for protected routes
- [ ] Unit tests for all functions
## Acceptance Criteria
- Tests pass
- TypeScript types complete
- No hardcoded secrets
## Context
See existing auth patterns in lib/auth.ts
Tips:
- Be specific about endpoints, file locations, and expected behavior
- Include acceptance criteria the agent can verify
- Reference existing code patterns to follow
- Break large features into smaller issues
Customization Points
Project-Specific Commands
Edit the prompts to match your stack:
# Instead of generic npm commands
npm run build
npm run test
# You might use
pnpm build
pytest tests/
cargo test
Phase Labels
Add phase labels for tracking progress:
gh label create "phase-1" --color "BFD4F2"
gh label create "phase-2" --color "D4C5F9"
The script detects these and includes them in context:
PHASE=$(echo "$ISSUE_LABELS" | grep -oE 'phase-[0-9]+' | head -1)
Iteration Limits
Control how many issues to process:
./scripts/loop-github.sh 5 # Process exactly 5 issues
./scripts/loop-github.sh -1 # Run until manually stopped
Error Handling
Crash Recovery
On startup, the script resets any stuck issues:
STUCK_ISSUES=$(gh issue list --label "in-progress" --state open ...)
for stuck_issue in $STUCK_ISSUES; do
gh issue edit "$stuck_issue" --remove-label "in-progress" --add-label "agent-ready"
done
Graceful Shutdown
Catches SIGINT/SIGTERM to reset the current issue:
trap cleanup SIGINT SIGTERM SIGHUP
cleanup() {
if [ -n "$CURRENT_ISSUE" ]; then
gh issue edit "$CURRENT_ISSUE" --remove-label "in-progress" --add-label "agent-ready"
fi
exit 0
}
Press Ctrl+C and the current issue returns to agent-ready instead of being stuck.
Implementation Failures
If Claude fails during implementation, the issue is moved back:
gh issue edit "$ISSUE_NUMBER" --add-label "agent-ready" --remove-label "in-progress"
sleep 10
continue
This allows retry on the next iteration.
Best Practices
1. Start Small
Begin with well-defined, isolated issues. Expand scope as you gain confidence.
2. Monitor Initially
Watch the first few runs closely. Adjust prompts based on what works.
3. Review Periodically
While the loop is autonomous, human review of completed work catches edge cases.
4. Use Phase Labels
Group related issues with phase-1, phase-2 labels to maintain logical order.
5. Provide Context
Reference existing patterns in issue descriptions. The agent reads CLAUDE.md for guidance.
6. Keep CLAUDE.md Current
The loop updates documentation automatically, building institutional knowledge over time.
Limitations
- Complex decisions – The agent follows instructions well but struggles with ambiguous requirements
- External dependencies – API integrations, database migrations may need human setup
- Security-sensitive code – Review authentication, authorization, and crypto implementations
- Architecture changes – Major refactors benefit from human planning first
Use the loop for well-defined tasks within an established codebase. Let humans handle the architecture and security-critical decisions.
Extending the Pattern
Slack/Discord Notifications
Add a webhook call when issues complete:
curl -X POST "$WEBHOOK_URL" -d "{\"text\": \"✅ Issue #$ISSUE_NUMBER completed\"}"
Multiple Repositories
Run separate loops per repo in different tmux windows:
tmux new-session -d -s project-a 'cd ~/project-a && ./scripts/loop-github.sh -1'
tmux new-session -d -s project-b 'cd ~/project-b && ./scripts/loop-github.sh -1'
Priority Labels
Modify the issue fetch to prioritize certain labels:
# High priority first
ISSUE_JSON=$(gh issue list --label "agent-ready" --label "priority-high" --state open --limit 1 --json number,title,body)
# Fall back to regular if none
if [ -z "$ISSUE_JSON" ]; then
ISSUE_JSON=$(gh issue list --label "agent-ready" --state open --limit 1 --json number,title,body)
fi
Complete Script
Here's the full loop-github.sh script. Customize the prompts and commands for your project:
#!/bin/bash
# loop-github.sh - Automated task execution loop
# Usage: ./scripts/loop-github.sh <iterations>
# Run in background: nohup ./scripts/loop-github.sh 100 > logs/agent.log 2>&1 &
#
# Features:
# - Agent Teams (up to 10 parallel agents)
# - TDD approach
# - QA with actual fixes
# - CLAUDE.md maintenance
# - Crash recovery: resets in-progress issues on startup
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
# Enable Claude Code Agent Teams
export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
# Track current issue for cleanup on crash
CURRENT_ISSUE=""
# Cleanup function for graceful shutdown
cleanup() {
echo ""
echo "[$(date)] Script interrupted. Cleaning up..."
if [ -n "$CURRENT_ISSUE" ]; then
echo "Resetting issue #$CURRENT_ISSUE to agent-ready..."
gh issue edit "$CURRENT_ISSUE" --remove-label "in-progress" --add-label "agent-ready" 2>/dev/null || true
fi
exit 0
}
# Trap signals for graceful shutdown
trap cleanup SIGINT SIGTERM SIGHUP
if [ -z "$1" ]; then
echo "Usage: $0 <iterations>"
echo "Example: $0 10 # Run 10 tasks"
echo "Example: $0 -1 # Run forever"
exit 1
fi
ITERATIONS=$1
COUNTER=0
echo "=========================================="
echo "Agent Loop Starting"
echo "Iterations: $ITERATIONS (-1 = infinite)"
echo "Agent Teams: ENABLED (up to 10 agents)"
echo "Repository: $(pwd)"
echo "=========================================="
# ============================================================
# STARTUP: Reset any stuck in-progress issues from previous crash
# ============================================================
echo ""
echo "Checking for stuck in-progress issues from previous run..."
STUCK_ISSUES=$(gh issue list --label "in-progress" --state open --limit 100 --json number --jq '.[].number' 2>/dev/null || echo "")
if [ -n "$STUCK_ISSUES" ]; then
echo "Found stuck issues: $STUCK_ISSUES"
for stuck_issue in $STUCK_ISSUES; do
echo "Resetting #$stuck_issue to agent-ready..."
gh issue edit "$stuck_issue" --remove-label "in-progress" --add-label "agent-ready" 2>/dev/null || true
done
echo "Cleanup complete."
else
echo "No stuck issues found."
fi
echo ""
# Helper function to add comment to issue
add_comment() {
local issue_num="$1"
local phase="$2"
local message="$3"
local timestamp=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
gh issue comment "$issue_num" --body "### $phase
$message
---
*🕐 $timestamp*"
}
while true; do
# Check iteration limit
if [ "$ITERATIONS" != "-1" ] && [ "$COUNTER" -ge "$ITERATIONS" ]; then
echo "Completed $COUNTER iterations. Exiting."
break
fi
COUNTER=$((COUNTER + 1))
echo ""
echo "=========================================="
echo "Iteration $COUNTER / $ITERATIONS"
echo "Time: $(date)"
echo "=========================================="
# Fetch first open issue with 'agent-ready' label, ordered by issue number (oldest first)
ISSUE_JSON=$(gh issue list --label "agent-ready" --state open --limit 100 --json number,title,body,labels --jq 'sort_by(.number) | .[0]')
ISSUE_NUMBER=$(echo "$ISSUE_JSON" | jq -r '.number // empty')
if [ -z "$ISSUE_NUMBER" ] || [ "$ISSUE_NUMBER" == "null" ]; then
echo "No agent-ready issues found. Waiting 60 seconds..."
sleep 60
continue
fi
ISSUE_TITLE=$(echo "$ISSUE_JSON" | jq -r '.title')
ISSUE_BODY=$(echo "$ISSUE_JSON" | jq -r '.body')
ISSUE_LABELS=$(echo "$ISSUE_JSON" | jq -r '.labels[].name' | tr '\n' ', ')
echo "📋 Working on Issue #$ISSUE_NUMBER: $ISSUE_TITLE"
echo "Labels: $ISSUE_LABELS"
# Track current issue for cleanup on crash
CURRENT_ISSUE="$ISSUE_NUMBER"
# Update labels: remove agent-ready, add in-progress
gh issue edit "$ISSUE_NUMBER" --add-label "in-progress" --remove-label "agent-ready"
# Get the phase from labels for context
PHASE=$(echo "$ISSUE_LABELS" | grep -oE 'phase-[0-9]+' | head -1 || echo "unknown")
# ============================================================
# STEP 1: Starting Work
# ============================================================
add_comment "$ISSUE_NUMBER" "🚀 Step 1: Starting Work" "
**Agent picked up this issue**
- **Task:** $ISSUE_TITLE
- **Approach:** TDD (Tests First)
- **Phase:** $PHASE
- **Mode:** Agent Teams (up to 10 parallel agents)
**Workflow:**
1. 🔨 Implementation (TDD with Agent Teams)
2. 📝 Summary
3. 🔍 QA Review & Fix
4. 📊 QA Report
5. 📚 CLAUDE.md Update
6. ✅ Complete
"
# ============================================================
# STEP 2: Implementation Phase
# ============================================================
add_comment "$ISSUE_NUMBER" "🔨 Step 2: Implementation" "
Starting TDD implementation with Agent Teams...
- Spawning up to 10 parallel agents for efficient work
- Writing tests first
- Implementing to make tests pass
"
# Construct the prompt for Claude - Implementation Phase
# CUSTOMIZE THIS FOR YOUR PROJECT
IMPL_PROMPT="
GitHub Issue #$ISSUE_NUMBER: $ISSUE_TITLE
Phase: $PHASE
## Issue Description:
$ISSUE_BODY
## Instructions - IMPLEMENTATION PHASE:
You are implementing features for this project.
### TDD Workflow:
1. **Read context**: Check any CLAUDE.md files for patterns
2. **Write tests FIRST**: Create failing tests that define expected behavior
3. **Implement**: Write code to make the tests pass
4. **Verify**: Run tests, type checks, and linting
5. **Commit**: Make descriptive commits referencing #$ISSUE_NUMBER
### Commands:
- Build: \`npm run build\`
- Test: \`npm run test\`
- Typecheck: \`npm run typecheck\`
- Lint: \`npm run lint\`
### Quality Requirements:
- All tests must pass
- No TypeScript errors
- Follow existing code patterns
## AGENT TEAMS INSTRUCTION:
Use Agent Teams with up to 10 parallel agents to efficiently complete this task.
Provide a detailed summary of:
1. What tests were written
2. What was implemented
3. How many agents were used
4. Commands run and their results
5. Any issues encountered
"
echo "🔨 Running Implementation Phase with Agent Teams..."
IMPL_RESULT=$(CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 claude --dangerously-skip-permissions -p "$IMPL_PROMPT" 2>&1) || {
echo "❌ Implementation failed"
add_comment "$ISSUE_NUMBER" "❌ Implementation Error" "
The agent encountered an error during implementation:
\`\`\`
$(echo "$IMPL_RESULT" | tail -c 2000)
\`\`\`
Moving back to agent-ready for retry.
"
gh issue edit "$ISSUE_NUMBER" --add-label "agent-ready" --remove-label "in-progress"
sleep 10
continue
}
IMPL_SUMMARY=$(echo "$IMPL_RESULT" | tail -c 4000)
# ============================================================
# STEP 3: Implementation Summary
# ============================================================
add_comment "$ISSUE_NUMBER" "📝 Step 3: Implementation Summary" "
**Implementation completed.**
\`\`\`
$(echo "$IMPL_SUMMARY" | head -c 3500)
\`\`\`
Proceeding to QA Review & Fix...
"
# ============================================================
# STEP 4: QA Review & Fix
# ============================================================
add_comment "$ISSUE_NUMBER" "🔍 Step 4: QA Review & Fix" "
Starting Quality Assurance with Agent Teams...
- Running full test suite
- Checking types and linting
- **Fixing any issues found**
"
QA_PROMPT="
GitHub Issue #$ISSUE_NUMBER: $ISSUE_TITLE
## QA REVIEW & FIX PHASE
You must perform Quality Assurance AND fix any issues found.
### Previous Implementation:
$IMPL_SUMMARY
### Your Tasks:
1. **Review All Changes**
\`\`\`bash
git diff HEAD~1
\`\`\`
2. **Run All Checks**
\`\`\`bash
npm run test
npm run typecheck
npm run lint
\`\`\`
3. **FIX ANY ISSUES** (Critical!)
- If tests fail → FIX THEM
- If type errors → FIX THEM
- If lint errors → FIX THEM
- Commit each fix: 'fix: description (#$ISSUE_NUMBER)'
4. **Verify Everything Works**
- Re-run all checks after fixes
5. **Final Status**
- All tests pass? ✅/❌
- All types pass? ✅/❌
- All lint pass? ✅/❌
YOU MUST ACTUALLY FIX ISSUES, NOT JUST REPORT THEM.
"
echo "🔍 Running QA Review & Fix with Agent Teams..."
QA_RESULT=$(CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 claude --dangerously-skip-permissions -p "$QA_PROMPT" 2>&1) || {
echo "⚠️ QA phase had issues"
QA_RESULT="QA phase encountered an error. Manual review may be needed."
}
QA_SUMMARY=$(echo "$QA_RESULT" | tail -c 4000)
# ============================================================
# STEP 5: QA Report
# ============================================================
add_comment "$ISSUE_NUMBER" "📊 Step 5: QA Report" "
## Quality Assurance Complete
$QA_SUMMARY
---
### All Changes
\`\`\`
$(git diff --name-only HEAD~2 2>/dev/null | head -30 || echo "Unable to get diff")
\`\`\`
### Recent Commits
\`\`\`
$(git log --oneline -5 2>/dev/null || echo "No commits")
\`\`\`
"
# ============================================================
# STEP 6: CLAUDE.md Update
# ============================================================
add_comment "$ISSUE_NUMBER" "📚 Step 6: CLAUDE.md Update" "
Updating CLAUDE.md files with learnings from this task...
"
CHANGED_DIRS=$(git diff --name-only HEAD~2 2>/dev/null | xargs -I{} dirname {} | sort -u | head -20 || echo ".")
CLAUDE_MD_PROMPT="
## CLAUDE.md Maintenance - Final Step
Issue #$ISSUE_NUMBER: $ISSUE_TITLE is complete.
### Changed Directories:
$CHANGED_DIRS
### Your Task:
Update CLAUDE.md files to document what was learned.
**Root CLAUDE.md** (create if missing):
- Repo purpose, global commands
- Routing: 'For X, see @path/CLAUDE.md'
- Under 500 lines
**Subdirectory CLAUDE.md** (for meaningful boundaries):
- What module does
- Local commands
- Patterns / Anti-patterns
- Gotchas from this task
Commit: 'docs: update CLAUDE.md for #$ISSUE_NUMBER'
"
echo "📚 Running CLAUDE.md Update with Agent Teams..."
CLAUDE_MD_RESULT=$(CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 claude --dangerously-skip-permissions -p "$CLAUDE_MD_PROMPT" 2>&1) || {
echo "⚠️ CLAUDE.md update had issues"
CLAUDE_MD_RESULT="CLAUDE.md update encountered an error."
}
CLAUDE_MD_SUMMARY=$(echo "$CLAUDE_MD_RESULT" | tail -c 2500)
add_comment "$ISSUE_NUMBER" "📚 Step 6 Complete: CLAUDE.md Updated" "
**Documentation updated:**
\`\`\`
$(echo "$CLAUDE_MD_SUMMARY" | head -c 2000)
\`\`\`
"
# ============================================================
# STEP 7: Task Complete
# ============================================================
COMMITS=$(git log --oneline -7 2>/dev/null | head -7 || echo "No commits")
add_comment "$ISSUE_NUMBER" "✅ Step 7: Task Complete" "
## Issue #$ISSUE_NUMBER Successfully Completed
### Summary
| Step | Status |
|------|--------|
| Implementation | ✅ Done |
| Tests Written | ✅ Done |
| QA Review & Fix | ✅ Done |
| CLAUDE.md | ✅ Updated |
### Commits
\`\`\`
$COMMITS
\`\`\`
---
*🤖 Completed by agent loop*
*📅 $(date -u +"%Y-%m-%d %H:%M:%S UTC")*
*🔄 Iteration $COUNTER*
"
# Close issue
gh issue close "$ISSUE_NUMBER"
gh issue edit "$ISSUE_NUMBER" --add-label "completed" --remove-label "in-progress"
# Clear current issue tracker
CURRENT_ISSUE=""
echo "✅ Issue #$ISSUE_NUMBER completed!"
# Push changes to remote
echo "📤 Pushing changes to remote..."
git push origin main --no-verify 2>/dev/null || echo "⚠️ Push failed (will retry later)"
sleep 5
done
echo "=========================================="
echo "Agent Loop Finished - $COUNTER iterations"
echo "=========================================="
Make it executable and run:
chmod +x scripts/loop-github.sh
./scripts/loop-github.sh -1
Conclusion
This pattern has processed hundreds of issues across my projects. It's not magic—the agent still needs well-structured issues and a codebase with good patterns. But for routine implementation work, it frees up significant time while maintaining code quality through TDD and automated QA.
Start with a few test issues, tune the prompts for your project, and scale up as you gain confidence. The crash recovery and graceful shutdown ensure you can run it 24/7 without babysitting.