Skip to content

GitHub Issues Work Source

The GitHub Issues work source adapter allows agents to automatically fetch, claim, and complete tasks from GitHub Issues. It uses a label-based workflow to track issue state and prevent multiple agents from working on the same issue.

agents/my-coder.yaml
name: my-coder
description: "Implements features from GitHub issues"
workspace: my-project
repo: myorg/my-project
work_source:
type: github
repo: myorg/my-project
labels:
ready: ready
in_progress: agent-working
auth:
token_env: GITHUB_TOKEN
schedules:
issue-check:
type: interval
interval: 5m
prompt: |
Check for ready issues and implement the oldest one.
work_source:
# Required: Adapter type
type: github
# Required: Repository in "owner/repo" format
repo: myorg/my-project
# Optional: Label configuration
labels:
# Label that marks issues as ready for agent work
ready: ready # default: "ready"
# Label applied when an agent claims the issue
in_progress: agent-working # default: "agent-working"
# Optional: Labels to exclude from processing
exclude_labels: # default: ["blocked", "wip"]
- blocked
- needs-design
- wip
# Optional: Re-add ready label when releasing work on failure
cleanup_on_failure: true # default: true
# Optional: Authentication configuration
auth:
# Environment variable containing the GitHub token
token_env: GITHUB_TOKEN # default: "GITHUB_TOKEN"
FieldTypeDefaultDescription
typestringMust be "github"
repostringRepository in owner/repo format
labels.readystring"ready"Label marking issues available for work
labels.in_progressstring"agent-working"Label applied when claiming
exclude_labelsstring[]["blocked", "wip"]Issues with these labels are skipped
cleanup_on_failurebooleantrueRe-add ready label on release
auth.token_envstring"GITHUB_TOKEN"Env var containing PAT

The GitHub adapter uses labels to manage work item state. This approach is:

  • Visible: Anyone can see issue status in the GitHub UI
  • Auditable: Label changes are tracked in issue history
  • Compatible: Works with existing GitHub workflows and automation
┌─────────────────────────────────────────────────────────────────────┐
│ GITHUB LABEL-BASED WORKFLOW │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ ISSUE CREATED │ │
│ │ (no workflow labels) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ Human adds │
│ "ready" label │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ READY FOR AGENT │ │
│ │ │ │
│ │ Labels: [ready] │ │
│ │ State: Open │ │
│ │ │ │
│ │ Agent can now claim this issue │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ Agent calls │
│ claimWork() │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ CLAIMED BY AGENT │ │
│ │ │ │
│ │ Labels: [agent-working] │ │
│ │ State: Open │ │
│ │ │ │
│ │ - "ready" label removed │ │
│ │ - "agent-working" label added │ │
│ │ - Other agents will skip this issue │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────┴─────────────────┐ │
│ │ │ │
│ Work succeeds Work fails │
│ completeWork() releaseWork() │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ COMPLETED │ │ RELEASED │ │
│ │ │ │ │ │
│ │ Labels: [] │ │ Labels: [ready] │ │
│ │ State: Closed │ │ State: Open │ │
│ │ │ │ │ │
│ │ - Comment posted │ │ - "agent-working" removed │ │
│ │ - Issue closed │ │ - "ready" label re-added │ │
│ │ │ │ - Comment posted (optional) │ │
│ └─────────────────────┘ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
StateLabelsAgent ActionResult
Available[ready]claimWork()Adds agent-working, removes ready
Claimed[agent-working]completeWork()Removes agent-working, closes issue (on success)
Claimed[agent-working]releaseWork()Removes agent-working, re-adds ready

When an agent completes work, it posts a structured comment:

Success:

## ✅ Work Completed
**Outcome:** success
**Summary:** Implemented user authentication feature
### Details
Added JWT-based authentication with refresh tokens.
Updated middleware to validate tokens on protected routes.
### Artifacts
- src/auth/jwt.ts
- src/middleware/auth.ts
- Pull request: #42

Failure:

## ❌ Work Completed
**Outcome:** failure
**Summary:** Could not implement feature due to unclear requirements
### Error

Missing database schema for user sessions


The GitHub adapter requires a Personal Access Token with the repo scope.

ScopePurpose
repoFull access to repositories (read/write issues, labels, comments)
  1. Go to GitHub Settings → Developer settings → Personal access tokens
  2. Click “Generate new token (classic)”
  3. Select the repo scope
  4. Set an appropriate expiration
  5. Generate and copy the token

For better security, use a fine-grained PAT with minimal permissions:

  1. Go to GitHub Settings → Developer settings → Fine-grained tokens
  2. Click “Generate new token”
  3. Set Repository access to “Only select repositories”
  4. Select the target repository
  5. Under Permissions, set:
    • Issues: Read and write
    • Metadata: Read-only (automatically selected)
  6. Generate and copy the token

Set the token as an environment variable:

Terminal window
export GITHUB_TOKEN=ghp_xxxxxxxxxxxx

Or specify a custom environment variable:

work_source:
type: github
repo: myorg/my-project
auth:
token_env: MY_GITHUB_PAT # Uses $MY_GITHUB_PAT instead

The adapter validates the token on first use:

  • Checks that the token is valid (not expired/revoked)
  • Verifies required scopes are present
  • Provides clear error messages if validation fails
GitHubAuthError: GitHub token is missing required scopes.
Found: [public_repo], Required: [repo]

GitHub’s API enforces rate limits to prevent abuse. The adapter includes robust handling for these limits.

ResourceAuthenticatedUnauthenticated
Core API5,000/hour60/hour
Search API30/minute10/minute

The adapter detects rate limiting from:

  • HTTP 403 with X-RateLimit-Remaining: 0
  • HTTP 429 (Too Many Requests)

When rate limited, the adapter automatically retries with exponential backoff:

Attempt 1: Wait 1 second
Attempt 2: Wait 2 seconds
Attempt 3: Wait 4 seconds
...
Maximum: Wait 30 seconds (configurable)

If the rate limit reset time is known, the adapter waits until then instead.

Configure a callback to be notified when rate limits are approaching:

// In advanced usage
const adapter = new GitHubWorkSourceAdapter({
type: "github",
owner: "myorg",
repo: "my-project",
rateLimitWarning: {
warningThreshold: 100, // Warn when < 100 requests remaining
onWarning: (info) => {
console.warn(`Rate limit warning: ${info.remaining}/${info.limit} remaining`);
console.warn(`Resets at: ${new Date(info.reset * 1000)}`);
}
}
});

Don’t poll too frequently. For most use cases, 5-minute intervals are sufficient:

schedules:
issue-check:
type: interval
interval: 5m # Good: 12 requests/hour
# interval: 30s # Bad: 120 requests/hour

Structure your agent prompts to batch operations:

prompt: |
Check for ready issues.
If found, work on ONE issue (the oldest).
Don't check for more issues until this one is complete.

The adapter exposes rate limit info after each request:

const result = await adapter.fetchAvailableWork();
const rateLimit = adapter.lastRateLimitInfo;
// { limit: 5000, remaining: 4850, reset: 1699999999, resource: "core" }

If you hit rate limits despite retries, the agent can wait and retry on the next schedule:

schedules:
issue-check:
type: interval
interval: 5m
prompt: |
Check for ready issues.
If you encounter rate limiting, report it and wait for the next run.

GitHub supports conditional requests with If-None-Match headers that don’t count against rate limits when content hasn’t changed. This is planned for future adapter versions.

ErrorCauseResolution
GitHubAPIError: rate limit exceededToo many requestsWait for reset, reduce polling frequency
GitHubAPIError: secondary rate limitAggressive concurrent requestsAdd delays between requests

When multiple agents work on the same repository, use specific labels to partition work:

agents/frontend-coder.yaml
name: frontend-coder
work_source:
type: github
repo: myorg/my-project
labels:
ready: frontend-ready
in_progress: frontend-working
# agents/backend-coder.yaml
name: backend-coder
work_source:
type: github
repo: myorg/my-project
labels:
ready: backend-ready
in_progress: backend-working

Use GitHub labels to set priority, then have agents process high-priority issues first:

prompt: |
Fetch all ready issues.
Work on issues in this order:
1. Issues labeled "critical" or "p0"
2. Issues labeled "high" or "p1"
3. All other issues (oldest first)

The adapter automatically infers priority from labels:

LabelsInferred Priority
critical, p0, urgentcritical
high, p1, importanthigh
low, p3low
(none of above)medium

For GitHub Enterprise installations, configure the API base URL:

work_source:
type: github
repo: myorg/my-project
# GitHub Enterprise Server
api_base_url: https://github.mycompany.com/api/v3

Control what happens when work is released:

work_source:
type: github
repo: myorg/my-project
# When agent fails/times out, should the issue go back to "ready"?
cleanup_on_failure: true # default: re-adds "ready" label
# cleanup_on_failure: false # leaves issue without "ready" label

Cause: The GITHUB_TOKEN environment variable is not set.

Solution: Export the token before running herdctl:

Terminal window
export GITHUB_TOKEN=ghp_xxxxxxxxxxxx
herdctl run

“GitHub token is missing required scopes”

Section titled ““GitHub token is missing required scopes””

Cause: The PAT doesn’t have the repo scope.

Solution: Generate a new token with the repo scope, or use a fine-grained token with Issues read/write permission.

Cause: Another agent (or process) claimed the issue first.

Solution: This is normal in multi-agent setups. The adapter returns already_claimed and the scheduler can try the next available issue.

Cause: The token doesn’t have access to the repository.

Solution: Ensure the token has access to the target repository. For fine-grained tokens, check repository access settings.

Cause: Polling too frequently or too many agents sharing limits.

Solutions:

  1. Increase polling interval (e.g., 5m → 15m)
  2. Use different tokens for different agents
  3. Reduce the number of concurrent agents

Here’s a full agent configuration for a production setup:

agents/issue-worker.yaml
name: issue-worker
description: "Implements features and fixes bugs from GitHub issues"
workspace: my-project
repo: myorg/my-project
# Identity
identity:
name: "Issue Worker"
role: "Software Engineer"
personality: "Methodical, writes tested code"
system_prompt: |
You are a software engineer working on this project.
When implementing an issue:
1. Read the issue description carefully
2. Explore the relevant code
3. Implement the solution
4. Write tests
5. Create a pull request
6. Report your results
# Work source configuration
work_source:
type: github
repo: myorg/my-project
labels:
ready: ready-for-dev
in_progress: agent-working
exclude_labels:
- blocked
- needs-design
- question
cleanup_on_failure: true
auth:
token_env: GITHUB_TOKEN
# Schedule
schedules:
continuous:
type: interval
interval: 5m
prompt: |
Check for GitHub issues labeled "ready-for-dev".
If issues are available:
1. Claim the oldest issue
2. Implement the solution
3. Create a PR linking to the issue
4. Report success with the PR URL
If no issues are available, report "No issues ready for work."
# Session settings
session:
max_turns: 100
timeout: 4h
model: claude-sonnet-4-20250514
# Permissions
permissions:
mode: acceptEdits
allowed_tools:
- Read
- Write
- Edit
- Bash
- Glob
- Grep
- Task
mcp_servers:
github:
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}