GitHub App setup (webhook trigger)¶
This document describes how to configure a GitHub App to trigger GitTinkerer runs via the issue_comment webhook.
Overview¶
- GitHub delivers an
issue_commentwebhook to the VPS service endpoint. - The service verifies the webhook signature.
- The service only reacts to comments that begin with
/tinker. - The service uses
issue.pull_request.urlfrom the webhook payload to fetch PR metadata (head/base refs and SHAs). - The service starts a run by spawning
bin/gittinkerer run --payload-file .... - GitTinkerer posts the final response as a new PR conversation comment (by PR number).
- Run artifacts are written to
artifacts/<timestamp>/agent-run/.
1) Create the GitHub App¶
In GitHub:
- Go to Settings → Developer settings → GitHub Apps → New GitHub App.
- Set Webhook URL to your VPS service endpoint:
https://<your-host>/api/github/webhook- Set a Webhook secret (random, high-entropy).
- Install the app on the org/repo that GitTinkerer will operate on.
2) Subscribe to events¶
Enable these webhook events:
- Issue comment
GitTinkerer only uses issue_comment events for PR-backed issues (where issue.pull_request exists).
3) Permissions (minimum)¶
The exact minimum can vary depending on whether you also use the App token for git pushes.
Recommended baseline permissions:
- Issues: Read & write
- Needed to read issue comments (incoming) and create a new comment for the agent response.
- Pull requests: Read-only
- Needed to fetch PR metadata (
head.ref,head.sha,base.ref) via the PR API URL. - Contents: Read & write (only if you plan to use the App token for git pushes over HTTPS)
If you push via SSH deploy keys instead, Contents write may not be required.
4) VPS environment variables¶
Configure the service with:
GH_WEBHOOK_SECRET- Used to verify
X-Hub-Signature-256.
For GitHub API authentication, you have two options:
Option A: GitHub App installation token (recommended)¶
Set:
GH_APP_IDGH_APP_PRIVATE_KEY- PEM-encoded private key (or use
service/private-key.pemas described below). GH_APP_INSTALLATION_ID(optional)- For webhook-triggered runs, the installation ID is read from the webhook payload (
hook.installation.id). - For web-mode runs (
POST /api/runswithsource: "web"), the service looks up the installation viaGET /repos/{owner}/{repo}/installation. - Only set
GH_APP_INSTALLATION_IDif you need to force a specific installation (e.g., custom flows that skip webhooks) or as a fallback when dynamic resolution is not possible.
The service will mint an installation access token at runtime and inject it into the spawned run as GITHUB_TOKEN, satisfying the CLI requirement.
4.1) Providing the private key¶
Download the GitHub App private key from GitHub App settings → Private keys → Generate and save the PEM file as service/private-key.pem. The service prefers this file and falls back to the GH_APP_PRIVATE_KEY env var if the file is missing or invalid.
Option B: Static token (fallback)¶
Set:
GITHUB_TOKEN
This should be an installation token or other GitHub token with the required scopes.
5) /tinker command behavior¶
- Only comments that begin with
/tinkertrigger a run. /tinkersupports multiline instructions.- The service strips
/tinkerand passes the remaining text ascomment_body. - The full raw comment body is recorded for audit in
pr_comment.txt(viacomment_raw_body).
Example:
6) Local testing¶
Quick-start for running locally with .env and a tunnel:
Then expose your local port (default 3000) via a tunneling tool (e.g., ngrok) and point the GitHub App webhook URL at the HTTPS tunnel.
Common options:
- Use a tunneling tool (e.g.
ngrok) to expose your local service and point the GitHub App webhook URL at the tunnel. - Use GitHub’s webhook “Recent Deliveries” UI to redeliver events.
Install ngrok on WSL Ubuntu:
sudo apt-get update && sudo apt-get install -y wget tar- Download ngrok v3 (x86_64):
wget https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz - For ARM64 laptops, use
ngrok-v3-stable-linux-arm64.tgz. tar -xvf ngrok-v3-stable-linux-*.tgz && sudo mv ngrok /usr/local/bin/- Add your auth token:
ngrok config add-authtoken <your-ngrok-token> - Verify version
ngrok version(should be 3.x). If you have an oldngrokin your PATH, remove or override it.
Using ngrok:
- Start the Node service (requires Node 24):
cd service && npm install && npm start(setSERVICE_PORT/SERVICE_HOST/ARTIFACTS_DIRas needed; defaults to port 3000). - Run
ngrok http <local-port>and copy the generated HTTPS forwarding URL. - In your GitHub App settings, set Webhook URL to
<ngrok-https>/api/github/webhookand keep your existing Webhook secret. - Trigger a fresh event (or redeliver in “Recent Deliveries”) and inspect the logs to confirm the request reaches your local service.
When debugging, verify:
- Signature verification is enabled (
GH_WEBHOOK_SECRETmatches the App setting). - Only
/tinkercomments trigger runs. - PR enrichment works (the service calls
issue.pull_request.url).
7) Security notes¶
- Always verify webhook signatures.
- Consider adding additional allowlisting (e.g., repo/org allowlist) before spawning runs.
- Treat comment bodies as untrusted input.
8) Webhook signature verification¶
Inline sequence of the verification path (Fastify webhook route → usecase → HMAC check):
sequenceDiagram
participant GH as GitHub
participant API as /api/github/webhook
participant Verify as verifyWebhook
participant HMAC as verifyGithubSignature
GH->>API: issue_comment webhook (raw Buffer + X-Hub-Signature-256)
API->>Verify: secret + rawBody + signature
Verify->>HMAC: sha256 HMAC + timingSafeEqual
HMAC-->>Verify: isValid
Verify-->>API: boolean (warn Sentry on failure)
API-->>GH: 401 if invalid
API-->>GH: continue if valid
- Secret comes from
GH_WEBHOOK_SECRET. - Verification happens in service/src/usecases/github/verifyWebhook.ts and delegates to the HMAC helper service/src/config/github.ts.
9) Webhook handling path¶
The Fastify controller processes GitHub deliveries as follows:
- Requires a Buffer body; otherwise returns 400 (service/src/controllers/githubWebhookController.ts).
- Verifies the signature; invalid requests get 401 (service/src/controllers/githubWebhookController.ts).
- Ignores non-
issue_commentevents with{ status: "ignored", reason: "unsupported_event" }(service/src/controllers/githubWebhookController.ts). - Parses JSON and validates against the
issue_commentschema (service/src/controllers/githubWebhookController.ts). - Logs repo/comment metadata and calls
handleIssueCommentwith a pooled DB client (service/src/controllers/githubWebhookController.ts). - Maps outcomes to HTTP: ignored → 200, rate_limited → 429, queued → 202 with
run_id(service/src/controllers/githubWebhookController.ts).
10) PR enrichment and run queue¶
handleIssueComment orchestrates the /tinker flow (service/src/usecases/github/handleIssueComment.ts):
- Skip if the comment is not on a PR-backed issue or lacks the
/tinkerprefix (service/src/usecases/github/handleIssueComment.ts). - Enforce repository rate limiting (service/src/usecases/github/handleIssueComment.ts).
- Fetch PR metadata via
fetchPullRequest, which calls the GitHub API client (installation-aware) to read head/base refs and SHAs (service/src/usecases/github/fetchPullRequest.ts). - Build the run payload (comment body, repo clone URL, PR refs, comment id) and create the run record (service/src/usecases/github/handleIssueComment.ts).
- Fire
startRunasynchronously; failures are captured in Sentry (service/src/usecases/github/handleIssueComment.ts).
11) GitHub App token minting¶
Inline sequence of how API tokens are produced and used:
sequenceDiagram
participant Service
participant Auth as getGithubToken
participant GitHub
Service->>Auth: need token (optional installationId)
alt App creds + installationId present
Auth->>Auth: build RS256 JWT (9 min exp)
Auth->>GitHub: POST /app/installations/{id}/access_tokens
GitHub-->>Auth: installation access token
else fallback
Auth-->>Service: GITHUB_TOKEN
end
Service-->>GitHub: API calls with Bearer token (e.g., pull request fetch)
- JWT building and token minting: service/src/config/github.ts.
- API client injects the token per request and sets GitHub headers: service/src/infra/github/api.ts.
12) Comment reply publishing¶
When a run completes for a PR source, the agent publishes a new issue comment on the PR conversation (lib/reply/github.sh):
- Only runs with
PAYLOAD_SOURCE=prand non-dry-run proceed; otherwise replies are skipped (lib/reply/github.sh). - Requires
GITHUB_TOKEN, PR number, repo full name, and the rendered response; missing fields fail fast with artifacts (lib/reply/github.sh). - Posts to
https://api.github.com/repos/{full_name}/issues/{pr_number}/commentsusingcurl; logs the resulting comment id (lib/reply/github.sh).
13) Troubleshooting webhook signatures¶
- Ensure
GH_WEBHOOK_SECRETmatches the GitHub App secret exactly (no whitespace trimming differences). - Confirm Fastify receives the raw Buffer body; JSON pre-parsing will break the HMAC check.
- Verify the
X-Hub-Signature-256header is present; GitHub only signs when a secret is set on the App. - Regenerate and redeploy the secret if you rotated it in GitHub but not in the service.
- If using a tunnel/proxy, ensure it preserves the body bytes; transformations (compression/re-encoding) will cause constant-time compare to fail.