Skip to main content

GitHub App Integration

wreck-it can be driven by a GitHub App webhook in addition to (or instead of) the cron-based headless runner. The webhook approach uses a Cloudflare Worker that reacts to GitHub events in real time, advancing the task state machine without waiting for the next scheduled run.

When configured with full GitHub App credentials (private key + app ID), the worker can also trigger cloud agents (create issues + assign Copilot) and merge pull requests — making it a fully autonomous alternative to the cron-based headless runner.

This document covers the full integration surface: which events trigger iterations, how state updates propagate, how labels are used, and how the webhook and cron modes interact.

Overview

GitHub Event (issues / push / pull_request)


┌─────────────────┐
│ Cloudflare │
│ Worker (WASM) │
│ │
│ 1. Verify sig │
│ 2. Parse event │
│ 3. Vend token │◄──── GitHub App (JWT → installation token)
│ 4. Read config │◄──── GitHub API (Contents)
│ 5. Read state │◄──── .wreck-it/config.toml + state branch
│ 6. Process iter │
│ 7. Trigger agent│────► GitHub API (Issues + GraphQL)
│ 8. Commit state │────► GitHub API (Contents) ─── only when a task is started
│ 9. Merge PRs │────► GitHub API (REST + GraphQL)
└─────────────────┘

The worker authenticates using the GitHub App's private key and app ID to generate a JWT, which is exchanged for an installation access token scoped to the specific repository from the webhook payload. This token has the permissions granted to the app, enabling the worker to manage the complete task lifecycle.

Events That Trigger Iterations

The worker subscribes to three GitHub webhook event types. All other events are ignored with an event ignored response.

EventAction filterWhen it fires
issuesopened or labeledAn issue is created or labeled and carries the wreck-it label
push(any)A push to any branch (typically the state branch after a state commit)
pull_requestclosed with merged: trueA pull request is merged (signals task completion)
pull_requestopened, ready_for_review, synchronizeApproves pending workflow runs and enables auto-merge for trusted PRs
pingGitHub's setup verification ping; the worker responds with pong

Issue events

The worker only processes issue events when the issue has the wreck-it label. Both the opened and labeled actions are handled:

  • opened + has label: Fires when wreck-it (or a user) creates an issue with the wreck-it label already applied.
  • labeled + has label: Fires when the wreck-it label is added to an existing issue.

This means creating an issue with the wreck-it label — which is exactly what the worker does when triggering an agent (see Agent Triggering below) — automatically triggers a webhook iteration.

Push events

Any push to the repository triggers an iteration. This is the primary mechanism for chaining: when the worker commits updated state to the state branch, the resulting push event fires another iteration, allowing the system to advance through multiple tasks in quick succession without waiting for the next cron tick.

Pull request events

Merged PRs trigger a dedicated handler that marks the corresponding task as complete, updates the state, and prepares the system to pick up the next task.

Non-merged PR events (opened, ready_for_review, synchronize) from trusted authors trigger workflow run approval: the worker approves any pending workflow runs so required checks can execute, then enables auto-merge when the base branch has required status checks. This handles cases where workflow runs need explicit approval before they can run (e.g. first-time contributors, outside collaborators, or fork PRs).

Agent Triggering

When the worker selects a new task (via the shared iteration logic in wreck-it-core), it also triggers a cloud coding agent:

  1. Creates a GitHub issue with the task description and the wreck-it + copilot labels.
  2. Discovers a coding agent via the suggestedActors GraphQL query (searches for copilot-swe-agent, copilot, claude, codex).
  3. Assigns the agent to the issue via the addAssigneesToAssignable GraphQL mutation.

This mirrors the same flow used by the headless CLI runner, so both modes produce identical issue + assignment patterns.

If agent triggering fails (e.g. no agent found, or insufficient permissions), the state still advances and the task is marked as in-progress. The next iteration or a cron run can retry the trigger.

PR Merging

When the worker receives a pull_request.closed event with merged: true:

  1. Marks the task as complete — finds the task associated with the merged PR and sets its status to Completed.
  2. Updates state — transitions to Completed phase and removes the PR from the tracked list.
  3. Commits state — writes updated task and state files to the state branch.

The worker also supports the full PR lifecycle management through its GitHub client:

  • Draft detection — identifies draft PRs and can mark them ready for review.
  • Merge status checks — determines whether a PR is mergeable, a draft, or has conflicts.
  • Direct merge — merges PRs using squash merge when no required checks exist.
  • Auto-merge — enables auto-merge via GraphQL for PRs with required status checks.
  • Branch protection — detects required status checks on the base branch.

Label Behavior

Labels added on issue creation

When the worker (or headless runner) triggers a cloud coding agent, it creates a GitHub issue via the REST API with two labels:

{
"title": "[wreck-it] <task-id>",
"body": "<task description + memory context>",
"labels": ["wreck-it", "copilot"]
}
LabelPurpose
wreck-itIdentifies the issue as wreck-it managed. Required for the webhook worker to process the corresponding issues event.
copilotConventional label indicating the issue is intended for a coding agent.

Both labels are applied at creation time (not added after the fact), so the issues.opened event already contains the labels. The webhook worker sees the wreck-it label and processes the event.

Labels on pull requests

wreck-it does not add labels to pull requests. PRs are created by the cloud coding agent (e.g. GitHub Copilot), not by wreck-it itself. The worker identifies relevant PRs by the closed + merged action, not by labels.

Labels on tasks

Tasks in tasks.json have an optional labels field for organizational metadata. These labels are local to the task file and are not automatically synced to GitHub issues or PRs.

State Commit Behavior

A key design concern is whether state commits can cause infinite event loops. The short answer: no — the worker only commits when there is real work to do.

When the worker commits

The worker writes updated task and state files to the state branch only when a task is started (IterationOutcome::TaskStarted). In the other two outcomes:

OutcomeFiles writtenCommitsTriggers push event
AllCompleteNoneNoNo
NoPendingTasksNoneNoNo
TaskStartedTask file + state fileYesYes

This means the event chain self-terminates: once all eligible tasks have been started (or all tasks are complete), the worker stops committing, which stops generating push events.

Event chain lifecycle

A typical chain looks like this:

1. Issue created with "wreck-it" label
└─► issues webhook fires
└─► Worker selects task A, creates agent issue, commits state
└─► push webhook fires
└─► Worker selects task B, creates agent issue, commits state
└─► push webhook fires
└─► Worker finds no pending tasks → no commit → chain ends

2. Agent completes task A, PR is merged
└─► pull_request webhook fires
└─► Worker marks task A complete, commits updated state

Headless CLI (cron mode) behavior

The cron-based headless runner (wreck-it run --headless) also avoids unnecessary commits:

  1. After the state machine loop, the state is serialized to disk via save_headless_state.
  2. commit_and_push_state checks for actual git changes (git diff --quiet + untracked file check).
  3. If the serialized state is byte-for-byte identical to what was already on disk, git detects no changes and no commit is made.
  4. Only when the state actually changed (e.g. a phase transition, a new task started) does a commit and push occur.

This ensures that a cron run that finds nothing to do does not produce a spurious commit that would trigger the webhook worker.

Worker vs. Cron: Two Modes of Operation

wreck-it supports two complementary ways to advance the state machine:

AspectWebhook WorkerCron Headless Runner
TriggerGitHub events (real-time)Scheduled cron (e.g. every 10 min)
RuntimeCloudflare Worker (WASM)GitHub Actions runner (native binary)
State accessGitHub Contents APIGit worktree on local filesystem
AuthenticationGitHub App JWT → repo-scoped installation tokenGITHUB_TOKEN (PAT)
Can trigger agentsYes (creates issues, assigns Copilot)Yes (creates issues, assigns Copilot)
Can merge PRsYes (via GitHub REST + GraphQL API)Yes (via GitHub REST + GraphQL API)

Both modes are now functionally equivalent. The webhook worker provides faster state advancement by reacting to events immediately, while the headless runner can serve as a fallback or complement for scenarios where webhook delivery is delayed.

Both modes are safe to run simultaneously. They operate on the same state branch but through different mechanisms (API vs. git). The concurrency group in the GitHub Actions workflow (cancel-in-progress: false) prevents overlapping cron runs.

Authentication

The worker generates a short-lived JWT from the app's private key and numeric ID, then exchanges it for an installation access token scoped to the specific repository from the webhook payload.

  • No static tokens to rotate — JWTs are generated on the fly and expire in 10 minutes.
  • Full permissions — the installation token inherits all permissions from the app configuration.
  • Repository-scoped — each token is limited to the single repository that triggered the webhook.

Setup

Prerequisites

1. Create a GitHub App

Create a GitHub App with the following permissions:

PermissionAccessPurpose
ContentsRead & writeRead config/state files, commit state updates
IssuesRead & writeCreate issues for agent triggers, read issue status
Pull requestsRead & writeCheck PR status, merge PRs, enable auto-merge
ActionsRead & writeApprove pending workflow runs

Subscribe to the following webhook events:

  • Issues — to trigger on issue creation / labeling
  • Push — to react to state branch changes
  • Pull requests — to detect merged PRs

Set the webhook URL to your deployed worker URL.

Generate a private key from the app settings page and note the App ID.

2. Configure and deploy the worker

# Install the WASM target
rustup target add wasm32-unknown-unknown

# Create the KV namespace
cd worker
wrangler kv namespace create WRECK_IT_STORE

Copy the id from the output and replace REPLACE_WITH_KV_NAMESPACE_ID in wrangler.toml:

[[kv_namespaces]]
binding = "WRECK_IT_STORE"
id = "<your-namespace-id>"
# Set secrets
wrangler secret put GITHUB_WEBHOOK_SECRET # Webhook secret from GitHub App settings
wrangler secret put GITHUB_APP_ID # Numeric App ID from the GitHub App settings
wrangler secret put GITHUB_APP_PRIVATE_KEY # PEM-encoded private key (paste the full key)
wrangler secret put API_TOKEN # Bearer token for the REST API endpoints

# Deploy
wrangler deploy

3. Required secrets

SecretRequiredPurpose
GITHUB_WEBHOOK_SECRETYesHMAC-SHA256 secret for verifying webhook payload signatures
GITHUB_APP_IDYesNumeric GitHub App ID
GITHUB_APP_PRIVATE_KEYYesPEM-encoded RSA private key from the GitHub App
API_TOKENYesBearer token for authenticating REST API requests

Source Code Reference

FilePurpose
worker/src/lib.rsWorker entry point — receives webhooks, verifies signatures, vends tokens, routes events
worker/src/github_app.rsGitHub App authentication — JWT generation and installation token vending
worker/src/webhook.rsHMAC-SHA256 signature verification, event type parsing
worker/src/github.rsGitHub REST + GraphQL API client (file read/write, issue creation, agent assignment, PR merging)
worker/src/processor.rsIteration logic — reads config/state, advances task machine, triggers agents, commits results
worker/src/types.rsDomain types (re-exports from wreck-it-core) and webhook payload types
cli/src/cloud_agent.rsCloud agent client — creates issues with labels, assigns Copilot
cli/src/headless.rsHeadless state machine — drives the full agent lifecycle in cron mode
cli/src/state_worktree.rsGit worktree management — commit/push with no-change detection
core/src/iteration.rsShared iteration logic used by both the worker and CLI

Next Steps