Skip to content

About

Cloudflare Worker and GitHub Actions for automated issue triage and PR review

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

aptu-github-app

A Cloudflare Worker and GitHub Actions that automate issue triage and PR review for any repository with the aptu GitHub App installed, powered by aptu. The Worker validates GitHub webhook signatures, reads each repository's .github/aptu.yml opt-in config, and dispatches repository_dispatch events to the originating repository to trigger aptu-powered triage, review, security scan, and issue lint workflows via reusable workflows.

Architecture

GitHub event (issues.opened|edited / pull_request.opened|synchronize|reopened|ready_for_review
              / issue_comment.created / pull_request_review_comment.created)
  -> Cloudflare Worker
       1. Verify X-Hub-Signature-256 (HMAC)
       2. Deduplicate by X-GitHub-Delivery (ReplayGuard Durable Object)
       3. Validate source IP against GitHub /meta hook CIDRs (fail-open)
       4. Get installation token (scoped to originating repo)
       5. Fetch .github/aptu.yml from originating repo
       6. Check per-installation quota (enforced for all installations)
       7. For PRs: skip draft PRs; evaluate review.paths filters
       8. Get dispatch token (scoped to originating repo)
       9. POST repository_dispatch to originating repo
  -> Caller's .github/workflows/aptu-{review,triage,scan-security,lint-issue}.yml (repository_dispatch handlers)
       -> Calls reusable workflow from aptu-github-app repo
            -> Checkout caller repo, run aptu CLI with caller's AI API key
  -> GitHub Reviews API (inline comments as github-actions[bot])

The Worker uses two Durable Objects: InstallationQuota for per-installation rate limiting (50 events per event type per 24h rolling window, enforced for all installations), and ReplayGuard for webhook deduplication. No external database is required.

Mention commands

Commenting @aptu on an issue or PR review comment triggers triage or review respectively. The commenter must be a collaborator with at least read permission on the repository.

Repository configuration

Each repository opts in by adding a .github/aptu.yml file. The Worker fetches this file on every webhook event; if the file is absent or invalid, no dispatch occurs.

Schema

version: 1                                        # required; must be 1

triage:
  enabled: true                                   # required if triage block present

review:
  enabled: true                                   # required if review block present
  instructions-file: .github/instructions/pr-review.md  # optional; path in the target repo
  skip-labeled: false                             # optional; aptu skips review if PR has a label
  paths:                                          # optional; suppress PR review when no file qualifies
    - "src/**"                                    # bare patterns are includes
    - "!src/data/**"                              # '!'-prefixed patterns are excludes

scan:
  enabled: true                                   # required if scan block present
  fail-on: critical,high                          # optional: comma-separated severities that fail the scan
  path: src/                                      # optional: root directory to scan (default ".")

lint:
  enabled: true                                   # required if lint block present
  spec: .github/lint-specs.toml                   # optional: lint spec path passed to aptu lint-issue --config

ai:
  provider: openrouter                            # required if ai block present
  model: google/gemma-4-26b-a4b-it                # required if ai block present

Scan configuration

The scan block enables aptu's local pattern-based security scanning. Scan runs without an AI provider -- no ai block is required. Results are uploaded as SARIF code scanning alerts to the originating repository. fail-on takes a comma-separated list of severities (critical, high, medium, low); the scan fails only if a finding matches one of the listed severities. Omitting fail-on reports findings without failing the check.

Field reference

Field Type Default Description
version integer -- Config schema version. Must be 1.
triage.enabled boolean -- Dispatch aptu-triage on issues.opened events.
review.enabled boolean -- Dispatch aptu-review on pull_request opened/synchronize/reopened/ready_for_review events.
review.instructions-file string .github/instructions/pr-review.md Path to PR review instructions file, relative to the target repo root. Passed to aptu as --instructions-file.
review.skip-labeled boolean false Passed to aptu as --skip-labeled. When true, aptu skips review if the PR already carries a label.
review.paths string[] -- Glob patterns (picomatch) evaluated against the full PR file list. Bare patterns are includes; !-prefixed patterns are excludes. A PR is dispatched if at least one file qualifies. If no file qualifies, the review dispatch is suppressed.
ai.provider string -- AI provider name passed to aptu as --provider. Supported: gemini, anthropic, openrouter. Required if ai block present. Also selects which of the caller's own repository secrets (GEMINI_API_KEY, ANTHROPIC_API_KEY, OPENROUTER_API_KEY) the dispatch handler resolves.
ai.model string -- AI model name passed to aptu as --model. Required if ai block present.
scan.enabled boolean false Enable aptu scan-security on pull requests. Runs local pattern-based secret scanning and uploads SARIF results to GitHub Code Scanning. No ai block required.
scan.fail-on string -- Comma-separated severities that fail the scan (critical, high, medium, low). Omit to report findings without failing the check.
scan.path string . Root directory to scan.
lint.enabled boolean false Dispatch aptu-lint-issue on issues.opened and issues.edited events. Deterministic linting of the issue body; advisory only (never fails the workflow).
lint.spec string -- Path to a lint spec file, relative to the target repo root. Passed to aptu as --config. Omit for default auto-discovery.
telemetry.enabled boolean false Opt in to POST anonymized review-context counters (reviews_total, truncation_events_total, files_truncated_total, budget_drop_reason_counts, finish_reasons_counts, model_tier_counts, prompt_budget_pct_histogram) to the hosted Worker for aggregate product analytics. No repo, PR, or actor identifiers are ever included; a failed or unreachable POST never fails the PR review job. Off by default.

All fields under triage, review, scan, lint, ai, and telemetry are validated strictly: unknown keys are ignored, but a missing enabled boolean causes the entire config to be rejected (no dispatch). Both ai fields (provider and model) are required if the ai block is present; a partial or empty-string block is rejected.

Telemetry rollup

When telemetry.enabled: true, the pr-review.yml workflow passes telemetry-rollup: true to the clouatre-labs/aptu action, which writes anonymized counters to .aptu/telemetry-rollup.jsonl. The workflow then POSTs the last JSONL line to the hosted Worker's POST /telemetry/rollup endpoint, which validates the payload against a strict key allowlist and merges it into a single global aggregate. The endpoint never returns anything but 202 Accepted, and the POST step uses continue-on-error: true plus shell-level || true, so a missing file, malformed payload, or network failure never fails the PR review job. See clouatre-labs/aptu's docs/GITHUB_ACTION.md for the exact per-field counter semantics.

Minimal opt-in example

This happy-path example parses with parseConfig and is the same example the welcome issue posted on installation suggests:

version: 1
triage:
  enabled: true
ai:
  provider: openrouter
  model: google/gemma-4-26b-a4b-it

The ai block contains no secret. It selects the provider and model, which determines which of your repository secrets the dispatch handler resolves: gemini -> GEMINI_API_KEY, anthropic -> ANTHROPIC_API_KEY, openrouter -> OPENROUTER_API_KEY. Create the matching secret yourself; the Worker cannot verify that it exists. Security scanning works without the ai block.

External installation example

Each installation provides its own AI API key via a GitHub repository secret in the caller's repo. The caller installs the dispatch handler workflows (see Installation) which receive repository_dispatch events and call reusable workflows from the clouatre-labs/aptu-github-app repository. The reusable workflow runs in the caller's context, using the caller's GITHUB_TOKEN for repository operations and the caller's AI API key secret for AI provider calls. This is the BYOK (bring your own key) model: the operator's AI keys are never exposed to any installation.

version: 1
triage:
  enabled: true
review:
  enabled: true
ai:
  provider: openrouter
  model: google/gemma-4-26b-a4b-it

Installation

Each repository that uses the aptu GitHub App must have these files:

  1. .github/aptu.yml -- opt-in configuration (see Repository configuration above)
  2. .github/workflows/aptu-review.yml -- dispatch handler for PR review
  3. .github/workflows/aptu-triage.yml -- dispatch handler for issue triage
  4. .github/workflows/aptu-scan-security.yml -- dispatch handler for security scans
  5. .github/workflows/aptu-lint-issue.yml -- dispatch handler for issue linting (requires lint.enabled: true in .github/aptu.yml and an aptu release containing the lint-issue command)

The Worker automatically provisions the four workflow files above when a repository is added to an installation. You only need to add .github/aptu.yml and configure an AI provider API key secret (OPENROUTER_API_KEY, ANTHROPIC_API_KEY, or GEMINI_API_KEY) in your repository. The GitHub App must have the Workflows permission enabled for automatic provisioning. For a step-by-step walkthrough, see docs/ONBOARDING.md.

Each dispatch handler receives its own repository_dispatch event type from the Worker and calls the appropriate reusable workflow hosted in clouatre-labs/aptu-github-app. The Worker mints an operation-scoped installation token and forwards it via client_payload.installation_token, which the dispatch handler passes into the reusable workflow's secrets: block. Installers do not need APP_ID or APP_PRIVATE_KEY secrets in their repository. The caller's AI API key secret is also passed to the reusable workflow via the secrets: block. Each handler resolves the key named after the ai.provider in .github/aptu.yml (OPENROUTER_API_KEY, ANTHROPIC_API_KEY, or GEMINI_API_KEY). Create that secret in your own repository or rely on an org-visible secret of the same name.

Deployment

Worker deployment

Merging to main triggers deploy.yml, which deploys the Worker to Cloudflare via bunx wrangler deploy.

GitHub secrets and variables:

  • CLOUDFLARE_API_TOKEN -- Cloudflare API token with Workers edit permission
  • CLOUDFLARE_ACCOUNT_ID -- Cloudflare account ID (repository variable)
  • APP_ID -- GitHub App ID (repository variable, required for Worker to mint installation tokens)
  • APP_PRIVATE_KEY -- GitHub App private key in PKCS#8 PEM format (repository secret, required for Worker)

AI API keys are configured per installation in the caller's repository, not in aptu-github-app. Each caller creates a repository secret named after the provider they select via ai.provider in their own .github/aptu.yml (OPENROUTER_API_KEY, ANTHROPIC_API_KEY, or GEMINI_API_KEY) -- never in the (static, unedited) dispatch handler.

Wrangler secrets (set via bunx wrangler secret put):

  • WEBHOOK_SECRET -- must match the value configured in the GitHub App
  • APP_PRIVATE_KEY -- GitHub App private key in PKCS#8 PEM format

Wrangler variables (in wrangler.toml [vars]):

  • APP_ID -- GitHub App ID
  • APTU_BOT_ID -- aptu bot user ID (used to ignore self-mentions)
  • SENTRY_DSN -- Sentry DSN for error tracking (optional; set via bunx wrangler secret put)

DNS prerequisite

The Worker is bound to the route aptu.dev/webhook via wrangler.toml. Cloudflare route bindings require a proxied DNS record for the zone root:

Type Name Content Proxied
AAAA aptu.dev 100:: Yes

100:: is the Cloudflare-documented placeholder for route-based Workers with no real origin.

App credentials

See clouatre-labs/aptu#94 for GitHub App registration and credential setup.

Operator procedures

See RUNBOOK.md for post-deploy steps, secret rotation, and incident response.

See PRIVACY.md for data categories, subprocessors, retention, and data residency.

Releases

Releases are cut manually by maintainers from GPG-signed annotated tags on main. See CONTRIBUTING.md for details.

Development

Prerequisites: Bun, Wrangler

bun install
bun test
bun run biome check
bunx wrangler dev

License

Apache-2.0. See LICENSE.

About

Cloudflare Worker and GitHub Actions for automated issue triage and PR review

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages