You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# ADR-44444: Per-Checkout GitHub App Auth for safe_outputs Git Operations
2
+
3
+
**Date**: 2026-07-09
4
+
**Status**: Draft
5
+
**Deciders**: Unknown
6
+
7
+
---
8
+
9
+
### Context
10
+
11
+
The `safe_outputs` job performs git operations (checkout, push, PR creation) on behalf of agents. Until this change, these operations used either the global `safe-outputs.github-app` setting or the default `GITHUB_TOKEN`. In cross-repo workflows—where the target repository belongs to a different organization than the workflow's home repo—the default token lacks push access, and the global safe-outputs token is coarse-grained (applies uniformly to all safe_outputs operations regardless of target). Agents that check out an external repo (e.g., `OrgB/target-repo`) need a way to supply a GitHub App credential scoped specifically to that checkout's safe_outputs git operations without altering the agent's own checkout authentication.
12
+
13
+
### Decision
14
+
15
+
We will add a `checkout.safe-output-github-app` field (with `checkout.safe-outputs-github-app` as a backward-compatible alias) to `CheckoutConfig`. This field carries a `GitHubAppConfig` used exclusively by the `safe_outputs` job when minting tokens for checkout/push operations targeting that repository. Token resolution in `resolvePRCheckoutToken` now checks the checkout manager first—preferring an explicit repository match, then `current: true`, then the default checkout—before falling back to the existing global safe-outputs token chain. The agent job's own checkout authentication is unaffected.
16
+
17
+
### Alternatives Considered
18
+
19
+
#### Alternative 1: Use the existing global `safe-outputs.github-app` setting
20
+
21
+
The global `safe-outputs.github-app` field already supports GitHub App credentials for all safe_outputs operations. Workflows could configure it once and rely on it for cross-repo pushes.
22
+
23
+
This was not chosen because the global setting is a blunt instrument: it applies to every safe_outputs operation regardless of target repository, making it unsuitable when multiple checkouts target different organizations with different app registrations. It also conflates the safe_outputs credential with the overall workflow credential, which may have wider permissions than needed for a specific checkout target.
24
+
25
+
#### Alternative 2: Add a `github-app` override field directly on each `safe_outputs` operation config
26
+
27
+
Each operation (`create-pull-request`, `push-to-pull-request-branch`) could accept its own `github-app` override, keeping auth configuration co-located with the operation that needs it.
28
+
29
+
This was not chosen because it would require duplicating the app config for every operation that targets the same repository, and it breaks the intuitive mapping between "a checked-out repository" and "the credentials used to push back to it." Anchoring the credential to the checkout config aligns with how `checkout.github-app` already works for agent auth, and lets the token-resolution logic leverage checkout ordering and the `current: true` flag.
30
+
31
+
### Consequences
32
+
33
+
#### Positive
34
+
- Cross-repo `safe_outputs` operations can use a fine-grained GitHub App token scoped to the exact target repository, without elevating the agent's own checkout credential.
35
+
- The resolution precedence (explicit repo match → `current: true` → default checkout) mirrors existing patterns in the checkout manager, keeping the mental model consistent.
36
+
-`ignore-if-missing` composes with the new field: when the app installation is absent, the resolver falls back to the global safe-outputs token chain transparently.
37
+
38
+
#### Negative
39
+
- Each checkout entry can now carry two separate GitHub App configs (`github-app` for agent auth, `safe-output-github-app` for safe_outputs auth), increasing configuration surface area and the potential for user confusion about which credential applies when.
40
+
- The backward-compatible `safe-outputs-github-app` alias adds parser complexity and a permanent dual-key code path that must be maintained.
41
+
42
+
#### Neutral
43
+
- The `resolvePRCheckoutToken` function signature gains a `*CheckoutManager` parameter; all existing callers that passed `nil` previously now pass an empty `NewCheckoutManager(nil)`, preserving existing behavior.
44
+
- Tests covering parser, checkout manager, step generator, and token resolution were added alongside the feature code, establishing coverage baselines for the new code paths.
45
+
46
+
---
47
+
48
+
*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.*
Copy file name to clipboardExpand all lines: docs/src/content/docs/reference/checkout.md
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,7 +51,7 @@ checkout:
51
51
| `path` | string | Path within `GITHUB_WORKSPACE` to place the checkout. Defaults to workspace root. |
52
52
| `github-token` | string | Token for authentication. Use `${{ secrets.MY_TOKEN }}` syntax. |
53
53
| `github-app` | object | GitHub App credentials (`client-id` or `app-id` (deprecated), `private-key`, optional `owner`, `repositories`). Mutually exclusive with `github-token`. `app` is a deprecated alias for the field name. Run `gh aw fix` to auto-migrate `app-id` to `client-id`. |
54
-
| `safe-output-github-app` | object | Optional per-checkout GitHub App credentials used exclusively for safe_outputs git operations on this checkout target (`client-id` or `app-id` (deprecated), `private-key`, optional `owner`, `repositories`). Does not change agent/activation checkout auth. See [Cross-Organization safe_outputs Authentication](#cross-organization-safe_outputs-authentication-safe-output-github-app) for cross-org usage. |
54
+
| `safe-outputs-github-app` | object | Optional per-checkout GitHub App credentials used exclusively for safe_outputs git operations on this checkout target (`client-id` or `app-id` (deprecated), `private-key`, optional `owner`, `repositories`). Does not change agent/activation checkout auth. See [Cross-Organization safe_outputs Authentication](#cross-organization-safe_outputs-authentication-safe-outputs-github-app) for cross-org usage. |
55
55
| `fetch-depth` | integer | Commits to fetch. `0` = full history, `1` = shallow clone (default). |
56
56
| `fetch` | string \| string[] | Additional Git refs to fetch after checkout. See [Fetching Additional Refs](#fetching-additional-refs). |
By default, the safe_outputs job uses `GITHUB_TOKEN` to check out repositories — `safe-outputs.github-app` and `safe-outputs.github-token` are **not** used for checkout and only govern PR/push API operations. This design prevents cross-org token confusion when the safe-outputs app is scoped to a target organization that differs from the workflow repository's organization.
160
160
161
-
For workflows that target a different organization, use `safe-output-github-app` on the relevant checkout entry to supply a checkout token for the safe_outputs job:
161
+
For workflows that target a different organization, use `safe-outputs-github-app` on the relevant checkout entry to supply a checkout token for the safe_outputs job:
162
162
163
163
```yaml wrap
164
164
checkout:
@@ -167,7 +167,7 @@ checkout:
167
167
client-id: ${{ vars.APP_CLIENT_ID }}
168
168
private-key: ${{ secrets.APP_PRIVATE_KEY }}
169
169
owner: OrgB
170
-
safe-output-github-app:
170
+
safe-outputs-github-app:
171
171
client-id: ${{ vars.APP_CLIENT_ID }}
172
172
private-key: ${{ secrets.APP_PRIVATE_KEY }}
173
173
owner: OrgB
@@ -184,7 +184,7 @@ safe-outputs:
184
184
185
185
In this configuration:
186
186
- The agent job checks out `OrgB/target-repo` using the app token scoped to `OrgB`.
187
-
- The safe_outputs job checks out `OrgB/target-repo` using the `safe-output-github-app` token.
187
+
- The safe_outputs job checks out `OrgB/target-repo` using the `safe-outputs-github-app` token.
188
188
- The safe_outputs job checks out the **workflow repository** using `GITHUB_TOKEN` (the default), not the `OrgB`-scoped app token — avoiding the cross-org authentication failure.
189
189
- `safe-outputs.github-app`is used only for PR creation and push operations, not for checkout.
Copy file name to clipboardExpand all lines: docs/src/content/docs/specs/checkout-behavior-specification.md
+6-6Lines changed: 6 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -82,11 +82,11 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S
82
82
### 3.1 Checkout Entry Parsing
83
83
84
84
`checkout:` MUST accept either a single object or an array of objects.
85
-
Each entry MAY define: `repository`, `ref`, `path`, `github-token` (or legacy `token`), `github-app` (or deprecated alias `app`), `safe-output-github-app`, `fetch-depth`, `fetch`, `sparse-checkout`, `submodules`, `lfs`, `current`, `wiki`, and `force-clean-git-credentials`.
85
+
Each entry MAY define: `repository`, `ref`, `path`, `github-token` (or legacy `token`), `github-app` (or deprecated alias `app`), `safe-outputs-github-app`, `fetch-depth`, `fetch`, `sparse-checkout`, `submodules`, `lfs`, `current`, `wiki`, and `force-clean-git-credentials`.
86
86
87
87
`github-token` and `github-app` MUST be mutually exclusive per entry.
88
88
89
-
`safe-output-github-app` applies only to safe_outputs git auth/token resolution. It MUST NOT change agent/activation checkout authentication behavior.
89
+
`safe-outputs-github-app` applies only to safe_outputs git auth/token resolution. It MUST NOT change agent/activation checkout authentication behavior.
90
90
91
91
### 3.2 Entry Merge Rules
92
92
@@ -95,7 +95,7 @@ Entries with the same `(repository, path, wiki)` key MUST merge with these rules
95
95
-`fetch-depth`: deepest wins (`0` wins over all)
96
96
-`ref`: first non-empty wins
97
97
- auth (`github-token` vs `github-app`): first auth wins
98
-
- safe_outputs auth (`safe-output-github-app`): first non-empty wins
98
+
- safe_outputs auth (`safe-outputs-github-app`): first non-empty wins
99
99
- sparse patterns: union
100
100
- fetch refs: union
101
101
-`lfs`: OR
@@ -144,7 +144,7 @@ Activation token precedence MUST be:
When safe_outputs checkout retention is enabled, checkouts without explicit entry tokens MUST persist the resolved PR checkout token so local git credentials match push/fetch token usage.
@@ -236,8 +236,8 @@ Effective side-repo token precedence MUST be:
-**T-CHK-011**: `checkout.safe-output-github-app` is the sole supported safe_outputs auth override per checkout entry
240
-
-**T-CHK-012**: safe_outputs checkout token MUST NOT use `safe-outputs.github-app` or `safe-outputs.github-token`; only `safe-output-github-app` (per entry) or `GITHUB_TOKEN` are permitted
239
+
-**T-CHK-011**: `checkout.safe-outputs-github-app` is the sole supported safe_outputs auth override per checkout entry
240
+
-**T-CHK-012**: safe_outputs checkout token MUST NOT use `safe-outputs.github-app` or `safe-outputs.github-token`; only `safe-outputs-github-app` (per entry) or `GITHUB_TOKEN` are permitted
241
241
-**T-CHK-013**: Checkout-manifest generation includes safe_outputs auth metadata without persisting resolved tokens
Copy file name to clipboardExpand all lines: pkg/parser/schemas/main_workflow_schema.json
+4Lines changed: 4 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -13170,6 +13170,10 @@
13170
13170
"$ref": "#/$defs/github_app",
13171
13171
"description": "GitHub App authentication. Mints a short-lived installation access token via actions/create-github-app-token. Mutually exclusive with github-token."
13172
13172
},
13173
+
"safe-outputs-github-app": {
13174
+
"$ref": "#/$defs/github_app",
13175
+
"description": "GitHub App authentication used only by safe_outputs checkout/fetch/push operations for this checkout target. Does not change activation/agent checkout authentication."
13176
+
},
13173
13177
"current": {
13174
13178
"type": "boolean",
13175
13179
"description": "Marks this checkout as the logical current repository for the workflow. When set to true, the AI agent will treat this repository as its primary working target. Only one checkout may have current set to true. Useful for central-repo workflows targeting a different repository."
0 commit comments