feat: add app installation plugin for managing GitHub App repo access#1012
Draft
decyjphr wants to merge 9 commits into
Draft
feat: add app installation plugin for managing GitHub App repo access#1012decyjphr wants to merge 9 commits into
decyjphr wants to merge 9 commits into
Conversation
Add a new plugin system where the target is a GitHub App installation rather than a repository. This enables managing which repos each app has access to (repository_selection) through the same config hierarchy (org → suborg → repo) that safe-settings uses for repo-level settings. New files: - lib/plugins/appInstallations.js: Plugin with delta + full sync modes - lib/appOctokitClient.js: Enterprise Octokit client with auto-batching at 50 repos per API call - lib/repoSelector.js: Repo resolution utility (name, team, properties) Integration: - settings.js: syncAppInstallations as separate phase after updateOrg - index.js: installation/installation_target webhook handlers for drift detection, enrichContextWithEnterprise helper Supports disable_plugins and additive_plugins. Enterprise slug is extracted from webhook payload (no env var needed). Closes #1005 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
decyjphr
commented
Jun 25, 2026
| if (enterprise && enterprise.slug) { | ||
| context.enterpriseSlug = enterprise.slug | ||
| try { | ||
| context.appGithub = await robot.auth() |
Collaborator
Author
There was a problem hiding this comment.
robot.auth() will not give an authenticated client with the enterprise as the installation target. Is that the goal here? In order to get the autneticated client for an enterprise slug, you would have to get all the installations and get the one with the name same as enterprise slug. See the syncInstallations method for how to do it
robot.auth() without args returns a JWT app client, not an enterprise installation-authenticated client. Fix enrichContextWithEnterprise to: 1. Use JWT client to list all installations 2. Find the enterprise installation matching payload.enterprise.slug 3. Call robot.auth(enterpriseInstallation.id) to get the properly authenticated client for enterprise API calls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Store the enterprise installation ID after the first successful lookup so subsequent calls to enrichContextWithEnterprise can skip the listInstallations API call and directly call robot.auth(cachedId). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
decyjphr
commented
Jun 25, 2026
| i => i.target_type === 'Enterprise' && i.account && i.account.slug === enterprise.slug | ||
| ) | ||
| if (enterpriseInstallation) { | ||
| context.appGithub = await robot.auth(enterpriseInstallation.id) |
Collaborator
Author
There was a problem hiding this comment.
We should cache the enterpriseInstallation ID so that next time we can just reuse it.
- Call enrichContextWithEnterprise in syncSelectedSettings (index.js) - Call syncAppInstallations with changedSubOrgs/changedRepos in syncSelectedRepos - Add _buildAppChangesFromDelta helper to extract affected apps from changed suborg/repo configs and compute repository_selection per app - syncAppInstallations now handles three modes: pre-computed delta, config-based delta (from changed files), and full sync Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Load previous version of changed suborg/repo configs using loadYamlFromRef - Compare old vs new app_installations sections to detect: - Apps removed from config → unselect all previously targeted repos - Targeting criteria changed → unselect repos no longer in scope - Selection takes precedence over unselection when both apply - Pass baseRef through syncAppInstallations → _buildAppChangesFromDelta Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Delta mode now skips apps configured as repository_selection: all at org level (org 'all' takes precedence — never add/remove repos via delta) - Fix repo config path used to load previous version from baseRef: use CONFIG_PATH/repos/<repo>.yml instead of bare repos/<repo>.yml (the bare path 404'd, so repo-level repository_unselection was never computed) - schema/settings.json: add app_installations top-level property and include it in additive_plugins and disable_plugins enums for editor validation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ng, docs - Remove installation.repositories_added/removed handler: an app only receives those events for its own installation, so they cannot detect drift on managed apps. Drift is reconciled by the scheduled full sync. - Process repository_unselection before repository_selection (delta and full sync) so a repo removed by one config and added by another ends up present - Add test asserting removal-before-addition ordering - Document app_installations in README (prerequisites, hierarchy, examples, sync behavior, drift note, disable/additive support) and add it to the configurable-items and disable_plugins lists Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In delta mode, when an app is present in both the previous and current suborg/repo config: - If suborg targeting is unchanged, skip the app entirely (no selection or unselection) — avoids re-adding all suborg repos on unrelated config edits - If targeting changed, emit only the diff (newly targeted repos to add, no-longer targeted repos to remove) instead of re-adding the full set - Repo-level: only select when the app is newly added to the repo config Add unit tests covering the skip, targeting-diff, and org-'all' precedence cases for _buildAppChangesFromDelta. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d/remove)
Rewrite appOctokitClient to the documented 2026-03-10 endpoints:
- org-scoped paths under /enterprises/{ent}/apps/organizations/{org}/...
- repository NAMES instead of IDs
- setRepositorySelection toggle for 'all'/'selected'
- PATCH /add and PATCH /remove, batched at 50
Update appInstallations plugin to pass org, drop ID resolution and
all-repo enumeration, use the toggle for 'all', and handle live
current_selection (all<->selected transitions) in full sync.
Thread current_selection through _computeFullAppDesiredState.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a new plugin system to safe-settings where the target is a GitHub App installation rather than a repository. This enables managing which repos each app has access to (
repository_selection) through the same config hierarchy (org → suborg → repo).Closes #1005
Changes
New files
lib/plugins/appInstallations.jslib/appOctokitClient.jslib/repoSelector.jstest/unit/lib/plugins/appInstallations.test.jstest/unit/lib/appOctokitClient.test.jstest/unit/lib/repoSelector.test.jsModified files
lib/settings.jssyncAppInstallationsas separate phase, registered inPLUGINSandADDITIVE_PLUGINSindex.jsinstallation/installation_targetwebhook handlers for drift detection,enrichContextWithEnterprisehelpertest/unit/lib/settings.test.jsDesign
Delta-based processing (efficient at scale)
Key features
Testing
All 292 unit tests pass (20 suites). 35 new tests added across 3 test files.