Skip to content

Add checkout.safe-output-github-app support for safe_outputs checkout auth#44444

Merged
pelikhan merged 4 commits into
mainfrom
copilot/checkoutsafe-outputs-github-app-again
Jul 9, 2026
Merged

Add checkout.safe-output-github-app support for safe_outputs checkout auth#44444
pelikhan merged 4 commits into
mainfrom
copilot/checkoutsafe-outputs-github-app-again

Conversation

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

This change adds checkout-scoped GitHub App auth for safe_outputs git operations, so cross-repo PR/push flows can use per-checkout app credentials instead of only global safe-outputs token/app settings. It also ensures token selection prefers the checkout target’s safe-output app when available.

  • Parser + model updates

    • Added CheckoutConfig.SafeOutputGitHubApp and merged state on resolved checkouts.
    • Added parsing for:
      • checkout.safe-output-github-app (canonical)
      • checkout.safe-outputs-github-app (compat alias)
    • Validation enforces object shape + required client-id|app-id and private-key.
  • Checkout manager token resolution

    • Added safe-output auth discovery helpers:
      • HasSafeOutputAppAuth()
      • ResolveSafeOutputCheckoutTokenExpression(targetRepo)
    • Resolution order for checkout-level safe-output app:
      1. explicit checkout matching target-repo
      2. current: true checkout
      3. default/root checkout
    • ignore-if-missing composes with default safe-output token fallback.
  • safe_outputs job wiring

    • Added generation of dedicated mint steps for checkout safe-output apps:
      • checkout-safe-output-app-token-{index}
    • Updated PR checkout token selection (resolvePRCheckoutToken) to prefer checkout-level safe-output app token before legacy safe-outputs token/app paths.
    • Plumbed the new resolver through shared safe_outputs checkout + handler-manager token env setup.
  • Example

checkout:
  - repository: OrgB/target-repo
    current: true
    github-app:
      client-id: ${{ vars.APP_CLIENT_ID }}
      private-key: ${{ secrets.APP_PRIVATE_KEY }}
    safe-output-github-app:
      client-id: ${{ vars.APP_CLIENT_ID }}
      private-key: ${{ secrets.APP_PRIVATE_KEY }}
      owner: OrgB

safe-outputs:
  create-pull-request:
    target-repo: OrgB/target-repo

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title Add support for checkout.safe-output-github-app Add checkout.safe-output-github-app support for safe_outputs checkout auth Jul 9, 2026
Copilot AI requested a review from pelikhan July 9, 2026 02:11
@pelikhan pelikhan marked this pull request as ready for review July 9, 2026 02:14
Copilot AI review requested due to automatic review settings July 9, 2026 02:14
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds checkout-scoped GitHub App authentication for safe_outputs git operations by extending checkout parsing/model, introducing a checkout-manager resolver for safe-output app tokens, and wiring new mint steps into the safe_outputs job so cross-repo PR/push flows can prefer per-checkout credentials.

Changes:

  • Extend checkout: parsing/model with safe-output-github-app (plus compat alias) and merge it into resolved checkout state.
  • Add CheckoutManager helpers to detect and resolve a checkout-scoped safe-output app token expression for a given target repo.
  • Update safe_outputs job step generation and token selection to mint and consume checkout-scoped safe-output app tokens.
Show a summary per file
File Description
pkg/workflow/github_token.go Adds checkout-manager-aware PR checkout token resolution + target-repo derivation helper.
pkg/workflow/github_token_test.go Adds unit tests covering new checkout-scoped safe-output token precedence + fallback behavior.
pkg/workflow/compiler_safe_outputs_steps.go Wires checkout-scoped safe-output app token mint steps and threads checkoutMgr into PR token resolution.
pkg/workflow/compiler_safe_outputs_steps_test.go Validates minted safe-output checkout app token step + credential env usage in generated steps.
pkg/workflow/checkout_step_generator.go Adds generator for safe-output checkout app token mint steps.
pkg/workflow/checkout_manager.go Adds SafeOutputGitHubApp to config/model, detection, and token-expression resolver (with ignore-if-missing composition).
pkg/workflow/checkout_manager_test.go Adds parsing + resolver + generator test coverage for safe-output checkout app auth.
pkg/workflow/checkout_config_parser.go Parses checkout.safe-output-github-app and checkout.safe-outputs-github-app alias with shape validation.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 8/8 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment thread pkg/workflow/github_token.go Outdated
Comment on lines +131 to +136
if checkoutMgr != nil {
targetRepo := resolvePRCheckoutTargetRepo(safeOutputs)
if checkoutToken, ok := checkoutMgr.ResolveSafeOutputCheckoutTokenExpression(targetRepo); ok {
return checkoutToken, true
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6b48467. resolvePRCheckoutToken now checks per-output github-token overrides before checkout-scoped safe-output app tokens, and I updated the precedence docs to match the actual order.

Comment on lines +62 to +64
if checkoutMgr.HasSafeOutputAppAuth() {
steps = append(steps, injectStepCondition(checkoutMgr.GenerateSafeOutputCheckoutAppTokenSteps(c, resolveCheckoutPermissions(data)), condition)...)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6b48467. checkout-safe-output-app-token-* mint steps are now emitted unconditionally when configured, so handler-manager token expressions do not depend on conditionally skipped mint steps.

Comment on lines 47 to 48
prCheckoutToken, _ := resolvePRCheckoutToken(data.SafeOutputs, checkoutMgr)
checkoutMgr.SetPushToken(prCheckoutToken)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6b48467. I stopped using prCheckoutToken as the global checkout push token fallback. Shared checkout steps now use resolveStaticCheckoutToken(...) for persisted checkout credentials, while the PR handler still uses the resolved PR token for git credential re-auth.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (360 new lines in pkg/workflow/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/44444-checkout-safe-output-github-app-auth.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-44444: Per-Checkout GitHub App Auth for safe_outputs Git Operations

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 49.9 AIC · ⌖ 13.6 AIC · ⊞ 8.5K ·
Comment /review to run again

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

⚠️ Test Quality Score: 73/100 — Acceptable

Analyzed 11 test(s): 10 design, 1 implementation, 0 violation(s).

📊 Metrics (11 tests)
Metric Value
Analyzed 11 (Go: 11, JS: 0)
✅ Design 10 (91%)
⚠️ Implementation 1 (9%)
Edge/error coverage 6 (55%)
Duplicate clusters 0
Inflation YES (compiler_safe_outputs_steps_test.go 4.4:1)
🚨 Violations 0
Test File Classification Issues
safe-output-github-app config is parsed checkout_manager_test.go design_test
safe-outputs-github-app alias is parsed checkout_manager_test.go design_test Missing assertion message on last assert.Equal
HasSafeOutputAppAuth / returns false checkout_manager_test.go design_test
HasSafeOutputAppAuth / returns true checkout_manager_test.go design_test
ResolveSafeOutputCheckoutTokenExpression / target-repo match checkout_manager_test.go design_test
ResolveSafeOutputCheckoutTokenExpression / empty target fallback checkout_manager_test.go design_test
ResolveSafeOutputCheckoutTokenExpression / ignore-if-missing checkout_manager_test.go design_test
TestGenerateSafeOutputCheckoutAppTokenSteps checkout_manager_test.go implementation_test Checks internal YAML string format; no error coverage
BuildSharedPRCheckoutSteps / safe-output app token wiring compiler_safe_outputs_steps_test.go design_test
ResolvePRCheckoutToken / safe-output app token priority github_token_test.go design_test
ResolvePRCheckoutToken / falls back to PAT github_token_test.go design_test
⚠️ Flagged Tests (2)

TestGenerateSafeOutputCheckoutAppTokenSteps (checkout_manager_test.go:~1218) — implementation_test. Verifies internal YAML string fragments ("id: checkout-safe-output-app-token-0", step name text) rather than behavioral outcomes. No error/negative coverage. Consider adding a test that exercises IgnoreIfMissing or verifying the step is correctly ordered relative to checkout steps.

safe-outputs-github-app alias is parsed (checkout_manager_test.go:~507) — design_test, minor style issue. The final assert.Equal(t, "${{ vars.SO_APP_ID }}", configs[0].SafeOutputGitHubApp.AppID) is missing a descriptive failure message, unlike the surrounding assertions in the same file.

Inflation flag: compiler_safe_outputs_steps_test.go adds 22 lines of test for 5 production lines (4.4:1 ratio). This is expected here — one new table-driven row exercises a behaviour that reuses existing infrastructure — but flagged per policy.

Verdict

Passed. 9% implementation tests (threshold: 30%). No guideline violations. Score deducted for test inflation in compiler_safe_outputs_steps_test.go (4.4:1 ratio) and modest edge-case coverage (55%).

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 39.8 AIC · ⌖ 24.2 AIC · ⊞ 6.8K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 73/100. 9% implementation tests (threshold: 30%). No guideline violations.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Add checkout.safe-output-github-app support

The approach is well-structured — clean model extension, proper alias handling, clear resolution precedence, and solid test coverage. Three issues need addressing before merge.

Blocking

  1. Closure mutates outer parameterResolveSafeOutputCheckoutTokenExpression assigns to the outer targetRepo parameter inside findSafeOutputAppCheckoutIndex. While harmless today (the closure is called once), it is a footgun and a code-correctness concern. See inline comment at checkout_manager.go:395.

  2. Stale docstringresolvePRCheckoutToken still documents the old 5-step precedence; the new checkout-level safe-output app token is now #1 but is missing from the list. See inline comment at github_token.go:117.

  3. Implicit coupling between two builder methodsbuildHandlerManagerStep creates a second CheckoutManager that may resolve to a step-output expression (checkout-safe-output-app-token-N) only emitted by buildSharedPRCheckoutSteps. If the two are ever decoupled, the GITHUB_TOKEN env value would be a dangling expression. See inline comment at compiler_safe_outputs_steps.go:263.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 93.2 AIC · ⌖ 8.59 AIC · ⊞ 4.8K

Comments that could not be inline-anchored

pkg/workflow/checkout_manager.go:395

The closure captures the outer targetRepo parameter and then mutates it with targetRepo = strings.TrimSpace(targetRepo). This modifies the outer variable. Use a local variable to avoid the accidental capture:

localTargetRepo := strings.TrimSpace(targetRepo)
if localTargetRepo != &quot;&quot; &amp;&amp; localTargetRepo != &quot;*&quot; {
    for idx, entry := range cm.ordered {
        ...
        if entry.key.repository == localTargetRepo {

This prevents subtle ordering-dependent bugs if the function is ev…

pkg/workflow/github_token.go:117

The resolvePRCheckoutToken docstring lists the old precedence order (1–5). The new checkout-level safe-output app token is now the highest priority (checked first), but the doc still lists "Per-config PAT" as #1. Please update the godoc to reflect the new order:

// Applies the following precedence (highest to lowest):
//  1. checkout.safe-output-github-app minted token (if configured for the target repo)
//  2. Per-config PAT: create-pull-request.github-token
//  3. Per-config PAT: pus…

</details>

<details><summary>pkg/workflow/compiler_safe_outputs_steps.go:263</summary>

`buildHandlerManagerStep` creates a fresh `NewCheckoutManager(data.CheckoutConfigs)` here, but the same `buildSharedPRCheckoutSteps` in the same compiler pass already built one at line 32. The second instantiation is consistent with the old code, but if the checkout-level token is resolved here it will reference step output IDs (`checkout-safe-output-app-token-N`) that are **only injected when `buildSharedPRCheckoutSteps` runs**. If `buildHandlerManagerStep` is ever invoked without `buildShared…

</details>

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Summary

Applied /tdd, /codebase-design, and /grill-with-docsrequesting changes on 3 substantive issues.

📋 Issues found (6 inline comments)
# File Issue
1 github_token.go:134 Stale precedence comment — per-config PAT no longer wins; comment says otherwise
2 checkout_manager.go:395 Missing edge-case test — target-repo exists but has no safe-output app
3 github_token.go:199 resolvePRCheckoutTargetRepo untested directly (only via integration)
4 github_token_test.go:198 Raw t.Fatalf where the rest of the suite uses require/assert
5 checkout_config_parser.go:149 Silent type coercion gap — non-string client-id produces a confusing error
6 checkout_manager.go:432 Dead app != nil guard — invariant already established two lines up

The architecture is solid and the overall test coverage is good. The stale precedence comment is the highest-priority fix — it directly documents a behaviour that is now incorrect.

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 100.2 AIC · ⌖ 5.96 AIC · ⊞ 6.6K ·
Comment /matt to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd, /codebase-design, and /grill-with-docs — requesting changes on stale documentation, missing test coverage, and a minor code clarity issue.

📋 Key Themes & Highlights

Key Themes

  • Stale precedence comment (github_token.go:151): the new early-return changes which token wins, but the comment block still claims per-config PAT has "highest precedence". This will mislead maintainers.
  • Test coverage gaps: the resolvePRCheckoutTargetRepo helper is untested directly; a "target-repo exists without safe-output app" scenario is unexercised.
  • Dead nil guard (checkout_manager.go:432): app != nil can never be false at that point — the invariant should be made explicit by removing the guard.
  • Test style inconsistency (github_token_test.go): two test cases use raw t.Fatalf where the rest of the suite uses require/assert.

Positive Highlights

  • ✅ Clean separation between checkout auth and safe-outputs auth — SafeOutputGitHubApp is a distinct field, avoiding silent credential bleed.
  • ✅ Backward-compatible alias (safe-outputs-github-app) is a good migration path.
  • ✅ Well-structured resolution precedence (explicit repo → current → default) mirrors the existing checkout-app pattern.
  • ✅ Comprehensive test coverage for the new parsing and step-generation paths.
  • ignore-if-missing composes correctly with the default fallback chain.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 100.2 AIC · ⌖ 5.96 AIC · ⊞ 6.6K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/workflow/github_token.go:134

[/tdd] The new early-return at lines 131–135 changes token precedence so checkout-level app now beats per-config PAT, but the comment at line 151 still says "Per-config PAT tokens take highest precedence" — that's now false.

<details>
<summary>💡 Suggested fix — update precedence doc above the function</summary>

Add a precedence block right above resolvePRCheckoutToken:

// Token resolution precedence:
//  1. checkout.safe-output-github-app token (explicit repo match → current →…

</details>

<details><summary>pkg/workflow/checkout_manager.go:395</summary>

**[/tdd]** Missing edge case: when `targetRepo` matches a checkout that has **no** `safeOutputApp`, the function continues to the `current: true` fallback, but the first loop already visited all entries. The behaviour is correct, but there is no test covering: `targetRepo` set, checkout exists for that repo, but without `safeOutputApp`verify it falls through to `current`.

&lt;details&gt;
&lt;summary&gt;💡 Suggested test&lt;/summary&gt;

```go
t.Run(&quot;target-repo exists but has no safe-output app falls back to…

</details>

<details><summary>pkg/workflow/github_token.go:199</summary>

**[/tdd]** `resolvePRCheckoutTargetRepo` has no test of its own — it&#39;s only tested indirectly via `resolvePRCheckoutToken`. The two checks (`CreatePullRequests` and `PushToPullRequestBranch`) and the precedence between them (create PR wins) are meaningful. A direct unit test would pin this contract.

&lt;details&gt;
&lt;summary&gt;💡 Suggested test structure&lt;/summary&gt;

```go
func TestResolvePRCheckoutTargetRepo(t *testing.T) {
    t.Run(&quot;create-pull-request repo wins&quot;, func(t *testing.T) { ... })
    t.Run…

</details>

<details><summary>pkg/workflow/github_token_test.go:198</summary>

**[/tdd]** This test uses `t.Fatalf` directly instead of the `assert`/`require` helpers used everywhere else in this file and test suite. Consider switching to `require.Equal` / `assert.Equal` for consistency and better diff output on failure.

&lt;details&gt;
&lt;summary&gt;💡 Suggested change&lt;/summary&gt;

```go
require.Equal(t, &quot;${{ steps.checkout-safe-output-app-token-0.outputs.token }}&quot;, token)
require.True(t, isCustom)

</details>

@copilot please address this.

pkg/workflow/checkout_config_parser.go:149

[/grill-with-docs] The validation at line 149 checks appConfig.AppID == &quot;&quot; but parseAppConfig silently ignores non-string values — it never returns an error. So if the user passes client-id: 123 (integer in YAML), AppID stays empty and the error message says "requires both client-id ... and private-key", which is confusing because the key was present.

This is a pre-existing gap in parseAppConfig, but the new validation surface increases the chance a user hits it.

<details>
<su…

pkg/workflow/checkout_manager.go:432

[/codebase-design] app can only be nil here when idx is valid (checked two lines up), so the app != nil guard is dead code — cm.ordered[idx].safeOutputApp was already confirmed non-nil by findSafeOutputAppCheckoutIndex. Removing the guard makes the invariant explicit and removes confusion for future readers.

<details>
<summary>💡 Suggested simplification</summary>

app := cm.ordered[idx].safeOutputApp
if app.shouldIgnoreMissingKey() {
    token = combineTokenExpressions(t</details>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — two correctness bugs in the token resolution chain must be fixed before merge.

Blocking issues (2 high)

1. Precedence inversion (github_token.go line 134)

The checkout safe-output app check runs before the per-config PAT block. This silently ignores any create-pull-request.github-token when a matching checkout safe-output-github-app exists — the opposite of what the existing // Per-config PAT tokens take highest precedence comment documents. Either reorder (PATs first) or update all documentation to reflect the new intentional order and add a test that pins it.

2. Silent fallthrough to wrong-repo app (checkout_manager.go line 404)

When targetRepo is set and a checkout for that repo exists but has no safeOutputApp, findSafeOutputAppCheckoutIndex skips it and matches a current: true checkout belonging to a different repository. The result is that cross-repo push operations receive the wrong app token with no diagnostic. Fix: return -1 when the target-repo checkout is found but lacks a safe-output app.

Medium issues (2)
  • Case-sensitive repo comparison (checkout_manager.go line 401): == instead of strings.EqualFold, inconsistent with the rest of the package — a case mismatch silently routes to the wrong checkout.
  • Missing test coverage for both bugs above: no test exercises per-config PAT + safe-output app simultaneously, and no test covers the target-repo-without-app fallthrough path.
Low issue (1)
  • Closure mutates outer parameter (checkout_manager.go line 395): targetRepo = strings.TrimSpace(targetRepo) inside the closure writes back to the enclosing function's parameter — use a local repo variable instead.

🔎 Code quality review by PR Code Quality Reviewer · 209 AIC · ⌖ 6.17 AIC · ⊞ 5.4K
Comment /review to run again

Comments that could not be inline-anchored

pkg/workflow/github_token.go:134

Precedence inversion: checkout safe-output app now silently overrides per-config PAT, contrary to the documented priority order.

<details>
<summary>💡 Details and suggested fix</summary>

The comment at line 151 (unchanged) still reads:

// Per-config PAT tokens take highest precedence (overrides GitHub App)

But the new block inserted at lines 131–136 runs before that check, so a checkout.safe-output-github-app now silently wins over create-pull-request.github-token. Any u…

pkg/workflow/checkout_manager.go:404

Silent fallthrough to unrelated checkout when target-repo has no safe-output app: wrong token used for push operations.

<details>
<summary>💡 Details and suggested fix</summary>

The inner loop in findSafeOutputAppCheckoutIndex uses continue to skip any checkout entry where safeOutputApp == nil. When targetRepo is set and there is a checkout entry for that repository, but it has no safeOutputApp, the code skips it and falls through to the second loop that picks the `current: …

pkg/workflow/checkout_manager.go:401

Case-sensitive repository comparison will miss legitimate matches: GitHub repo names are case-insensitive.

<details>
<summary>💡 Details and suggested fix</summary>

if entry.key.repository == targetRepo {  // byte-exact, wrong

GitHub treats Owner/Repo and owner/repo as the same repository. If checkout.repository and safe-outputs.create-pull-request.target-repo differ in case, this exact-match check silently fails and falls through to the current: true or default-check…

pkg/workflow/checkout_manager.go:395

Closure mutates the enclosing function's parameter (targetRepo), a subtle footgun.

<details>
<summary>💡 Details and suggested fix</summary>

findSafeOutputAppCheckoutIndex := func() int {
    targetRepo = strings.TrimSpace(targetRepo)  // mutates outer param

Go closures capture by reference, so this = assignment writes back to the targetRepo parameter of ResolveSafeOutputCheckoutTokenExpression. It is harmless today only because the closure is called exactly once and `t…

pkg/workflow/github_token_test.go:514

Missing test: per-config PAT + checkout safe-output app both configured — the regression path introduced by this PR has no coverage.

<details>
<summary>💡 Suggested test to add</summary>

The precedence inversion (checkout safe-output app now wins over per-config PAT) should at minimum be tested and documented. Add:

t.Run(&quot;checkout safe-output app wins over per-config PAT&quot;, func(t *testing.T) {
    safeOutputs := &amp;SafeOutputsConfig{
        CreatePullRequests: &amp;CreatePullRequestsCon</details>

<details><summary>pkg/workflow/checkout_manager_test.go:297</summary>

**Missing test: target-repo checkout exists but has no safe-output app**the silent fallthrough behaviour is unspecified and untested.

&lt;details&gt;
&lt;summary&gt;💡 Suggested test to add&lt;/summary&gt;

This is the edge case that triggers the correctness bug noted on `checkout_manager.go` line 404. Add a test that makes the intended semantics explicit:

```go
t.Run(&quot;returns no match when target-repo checkout lacks safeOutputApp&quot;, func(t *testing.T) {
    cm := NewCheckoutManager([]*CheckoutConfig{
      …

</details>

@pelikhan

pelikhan commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finished skill

@pelikhan

pelikhan commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

@copilot only support checkout.safe-outputs-github-app field name, remove compat for variant. Update compiler, schema and specs

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finished skill

Completed in 6b48467. I ran the PR finisher pass, fixed the three unresolved review-thread issues (token precedence, unconditional checkout safe-output app minting, and checkout push-token scoping), re-ran local validation (make fmt, make lint, make test-unit, make test, make recompile), and replied on each addressed thread.

@pelikhan

pelikhan commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot only support checkout.safe-outputs-github-app field name, remove compat for variant. Update compiler, schema and specs

Implemented in b54d08a. Compiler/parser now supports only checkout.safe-outputs-github-app; checkout.safe-output-github-app is rejected with a migration error. I also updated the main workflow schema, checkout specs/reference docs, and checkout parser tests accordingly.

@pelikhan pelikhan merged commit f78aa11 into main Jul 9, 2026
29 of 30 checks passed
@pelikhan pelikhan deleted the copilot/checkoutsafe-outputs-github-app-again branch July 9, 2026 03:26
Copilot stopped work on behalf of pelikhan due to an error July 9, 2026 03:26
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants