ADR: GitHub App Installation Identity Is Provider-Authoritative; Nango Is Secondary
Status: Accepted Date: 2026-03-10 Author: Spec Kitty team + Codex Related ADR: Connector Installation, User Link, and Resource Mapping Separation
Context
The connector hard-cutover introduces installation-first integrations across providers. During planning, one ambiguity kept recurring:
- should Spec-Kitty treat a Nango
connection_idas the canonical identity for a GitHub App installation? - or should Spec-Kitty treat GitHub's own installation identifier as the source of truth and only use Nango as an optional credential helper?
This distinction matters because GitHub Apps are not shaped like ordinary workspace OAuth connections.
GitHub's provider-native model is:
- One GitHub App registration has one webhook configuration
- Each customer install creates a GitHub installation
- Webhook payloads for GitHub App events include the installation object
- Installation access tokens are minted for a specific installation ID
- Optional user-to-server tokens are separate from the shared installation and only apply when the app needs to act on behalf of a user
That means GitHub itself recognizes installation identity and webhook authenticity independently of any Nango-side connection naming.
If Spec-Kitty incorrectly treats the Nango connection_id as the primary authority, agents will keep making the same design mistakes:
- routing inbound webhooks through Nango identifiers instead of GitHub installation identity
- storing GitHub install state as if it were generic OAuth workspace state
- coupling app-level webhook verification to team-level or mapping-level secrets
- designing future migrations around renaming Nango connections instead of persisting provider-native identifiers
Decision
For GitHub App integrations, GitHub provider identity is authoritative.
Spec-Kitty must model GitHub App installations as follows:
provider_installation_idis the canonical external identity of a GitHub installation- GitHub webhook verification uses the app-level webhook secret configured on the GitHub App registration
- Inbound webhook routing resolves from the payload's
installation.idand repository identity to a local installation plus repo mapping control_plane_connection_idis optional and secondary; if Nango is used, it is an implementation detail for outbound credential retrieval, not the canonical installation key- Optional user-level GitHub authorization is a separate concern from the shared installation and should be modeled like a user link, not as the installation itself
Rules For Agents
When working on GitHub App design or implementation, agents must follow these rules:
- Never use Nango
connection_idas the sole or primary identifier for a GitHub App installation - Always persist GitHub's installation identity on the installation record
- Treat webhook URL and webhook secret as app-level configuration, not per-team or per-mapping state
- Route GitHub events by
installation.idplus repository identity, then resolve explicit repo mapping - Treat repo mappings as the routing authority; Git metadata may suggest mappings but does not replace them
- Keep user-to-server tokens separate from installation-to-server auth
Implementation Consequences
Data model
TeamServiceInstallation.provider_installation_idis required for GitHubTeamServiceInstallation.control_plane_connection_idmust be nullable- repo access and explicit repo mappings live beneath the installation
Webhooks
- GitHub App ingress should be an app-level endpoint, not a binding-specific or team-specific endpoint
- signature validation uses the app webhook secret
- payload handling must extract
installation.idand repository identity before local routing
Outbound GitHub API calls
- the default authority chain is:
- app private key / app auth
- GitHub
installation_id - installation access token minted for that installation
- if Nango is used, it may help acquire or manage outbound credentials, but it does not replace the provider installation identifier in Spec-Kitty's domain model
User links
- user links are only needed for user-attributed operations
- user links must not replace shared installation state
Alternatives Considered
Treat Nango connection_id as the canonical GitHub installation identity
Rejected.
This hides the provider-native installation boundary and makes inbound routing dependent on an internal control-plane key that GitHub does not send on webhook payloads.
Treat GitHub App install as ordinary workspace OAuth
Rejected.
GitHub App auth and webhook flow are installation-based, not simple workspace-token based.
Put webhook secret on each repo mapping
Rejected for GitHub App.
GitHub App webhook verification is configured at the app registration boundary. Repo mappings are downstream routing state, not webhook-auth boundaries.
Operational Guidance
- Forced reconnect or reinstall flows must preserve the distinction between:
- GitHub installation identity
- optional Nango connection identifiers
- optional user authorization
- Migration planning must assume that old Nango connection naming can change without redefining what the GitHub installation is
- Observability should log both local installation UUID and
provider_installation_idfor GitHub events