GitHub Integration for OS
Captured: 2026-05-13
Objective#
Add a GitHub integration to OS that mirrors the Slack integration shape:
- GitHub App installation and connection state are project-scoped.
- Inbound GitHub webhooks are verified, resolved to the claimed project, and appended to a project integration stream.
- itx scripts and agents get a GitHub API capability backed by Octokit and a GitHub App installation.
Current Findings#
Legacy apps/os GitHub integration was removed with the OS1 app. OS should implement GitHub using its existing stream and capability model from scratch.
Relevant OS foundations already exist:
project_connectionsstores provider connection rows.project_connections.webhook_provider_identifiersupports globally unique webhook routing claims, currently used by Slack team IDs.project_secretsstores project secrets for Slack/Google.oauth_statesstores short-lived provider callback state.src/domains/secrets/integration-api.tsroutes Slack/Google callbacks and Slack webhooks.- Slack appends raw webhooks to
/integrations/slackasevents.iterate.com/slack/webhook-received. - The Slack integration exposes
itx.integrations.slack.get().*through its wrapped WebClient and project connection secret.
Target Shape#
Use provider name github in OS, not legacy github-app.
project_connections row:
provider:githubexternal_id: GitHub installation ID as a stringwebhook_provider_identifier: GitHub installation ID as a stringprovider_data:installationIdaccountIdaccountLoginaccountTyperepositorySelectioninstalledByUserId
Route inbound GitHub webhooks by payload.installation.id. This is the GitHub
equivalent of Slack routing by team_id.
Do not persist GitHub installation access tokens as long-lived project secrets. GitHub App installation tokens expire quickly, so the GitHub itx capability should mint fresh installation-scoped Octokit clients from runtime GitHub App config.
Runtime Config#
Extend AppConfig.integrations with GitHub App config:
appIdappSlugprivateKeywebhookSecretoauthClientIdif using GitHub user OAuth during installoauthClientSecretif using GitHub user OAuth during install
Prefer Octokit's GitHub App client for installation-scoped API access:
new App({ appId, privateKey, webhooks: { secret } })app.getInstallationOctokit(installationId)
Keep webhook signature verification explicit in the fetch handler, like Slack, rather than depending on Node middleware.
Integration API#
Add handling in src/domains/secrets/integration-api.ts:
/api/integrations/github/callback/api/integrations/github/webhook
Callback flow:
- Consume
oauth_statesfor providergithub. - Require Clerk callback user to match the state user.
- Read
installation_id. - Build a GitHub App client.
- Fetch installation/account metadata.
- Check whether the installation ID is already claimed by another project.
- Upsert the
project_connectionsrow. - Append
events.iterate.com/github/connectedto/integrations/github. - Redirect back to the original callback URL.
Webhook flow:
- Read the raw body.
- Verify
x-hub-signature-256using configured webhook secret. - Require
x-github-eventandx-github-delivery. - Parse JSON payload.
- Resolve
payload.installation.id. - Find the claimed project by
(provider, webhook_provider_identifier). - Append to
/integrations/github.
Suggested raw webhook event:
{
type: "events.iterate.com/github/webhook-received",
idempotencyKey: `github-webhook:${deliveryId}`,
payload: {
action,
body,
deliveryId,
githubEvent,
headers: {
githubDelivery,
githubEvent,
},
installationId,
repositoryFullName,
},
}Project UI#
Add integrations capability methods next to Slack/Google:
getGithubConnectionstartGithubInstallFlowdisconnectGithublistGithubRepositoriesif repo selection is needed
Update the integrations page to add a GitHub card with connect, disconnect, connection metadata, token/material status where relevant, and scope/permission summary if useful.
For install start, return:
https://github.com/apps/{appSlug}/installations/new?state={state}itx Capability#
Add a GitHub capability entrypoint under the GitHub domain and wire it into the current worker/domain entrypoint pattern.
Recommended first provider API:
itx.github.request({
route: "GET /repos/{owner}/{repo}/issues",
owner,
repo,
per_page: 20,
});
itx.github.paginate({
route: "GET /repos/{owner}/{repo}/pulls",
owner,
repo,
state: "open",
});Capability behavior:
- Require
DBbinding andprojectIdprops. - Read the project GitHub connection.
- Extract
installationId. - Parse OS
AppConfig. - Create a GitHub App client.
- Get an installation-scoped Octokit.
- Dispatch
octokit.request(...)oroctokit.paginate(...). - Return a stable JSON response shape.
Register the provider in:
- the platform project capability set
- any agent-local capability set that should expose GitHub
- itx and agent tests where capability lists are asserted
Optional GitHub Processor#
If GitHub webhooks should wake or drive agents, add a shared stream processor after the base integration works.
Processor target:
- Mounted on
/integrations/github. - Slug:
github. - Reduces connected/disconnected state.
- Consumes
events.iterate.com/github/webhook-received. - Routes pull request events to a stream like
/agents/github/{owner}/{repo}/pulls/{number}. - Routes issue events to a stream like
/agents/github/{owner}/{repo}/issues/{number}. - Forwards the original webhook unchanged to the routed stream.
- Leaves agent semantics to a downstream GitHub agent processor or generic agent setup.
This should be a second slice. The first slice should prove connection, webhook ingestion, and API capability.
Dependencies#
- Add
octokittoapps/os/package.json. - Regenerate lockfile with pnpm.
- If schema changes are needed, use sqlfu:
- update
src/db/definitions.sql - run
pnpm sqlfu:generate - check generated migrations
- update
No schema change is expected for the basic implementation because the existing connections/secrets/oauth tables are generic enough.
Testing Plan#
- App config parsing accepts GitHub runtime config.
- Install URL generation creates a state row and correct GitHub App install URL.
- Callback rejects missing/expired state and user mismatch.
- Callback upserts a GitHub connection and emits a connected event.
- Callback rejects an installation already claimed by another project.
- Webhook handler rejects missing/invalid signature.
- Webhook handler rejects missing delivery/event/installation ID.
- Webhook handler appends a deduped raw event to
/integrations/github. - GitHubCapability fails clearly when no project connection exists.
- GitHubCapability uses an installation-scoped Octokit client and dispatches
requestandpaginate. - Integrations UI shows connected and disconnected states.
Risks and Open Questions#
- GitHub App permissions need deliberate design. The App settings must include every REST operation agents should use, such as contents, issues, pull requests, checks/actions, statuses, and metadata.
- Decide whether GitHub user OAuth is needed during installation. A pure App installation flow may be enough for project-level access.
- Decide whether OS1 config repo behavior belongs in OS. Current evidence says no for the first slice.
- Decide how PR/issue agent routing should work before adding a GitHub processor. Slack-style routing is a good model, but GitHub route keys and stream path naming need explicit product decisions.