From da672ad67afc1c6bee989b944a52b83bf7439a70 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Fri, 19 Jun 2026 11:36:16 +0100 Subject: [PATCH 1/3] Fix duplicate Authorization header on git ops in push_to_pull_request_branch The safe_outputs job set http..extraheader from two sources: the persist-credentials checkout (.git/config) and the handler injecting a second value via GIT_CONFIG_* env (getGitAuthEnv). Since the key is multi-valued, git sent both Authorization headers, causing the server to reject requests with "Duplicate header: 'Authorization'" / HTTP 400. - push_to_pull_request_branch.cjs no longer injects gitAuthEnv; it relies on the credentials persisted in .git/config by the checkout, matching create_pull_request.cjs. - The safe_outputs checkout now persists the resolved PR push token (resolvePRCheckoutToken) so the single retained credential is correct, also fixing custom-PAT / cross-repo push configurations. Fixes #40280 --- .github/workflows/avenger.lock.yml | 1 + .github/workflows/changeset.lock.yml | 1 + .../workflows/chaos-pr-bundle-fuzzer.lock.yml | 1 + .github/workflows/ci-coach.lock.yml | 1 + .github/workflows/cloclo.lock.yml | 1 + .../workflows/code-scanning-fixer.lock.yml | 1 + .github/workflows/code-simplifier.lock.yml | 1 + .github/workflows/craft.lock.yml | 1 + ...aily-agent-of-the-day-blog-writer.lock.yml | 1 + .../daily-architecture-diagram.lock.yml | 1 + ...strostylelite-markdown-spellcheck.lock.yml | 1 + .../daily-caveman-optimizer.lock.yml | 1 + .../daily-community-attribution.lock.yml | 1 + ...ly-compiler-threat-spec-optimizer.lock.yml | 1 + .github/workflows/daily-doc-healer.lock.yml | 1 + .github/workflows/daily-doc-updater.lock.yml | 1 + .../daily-rendering-scripts-verifier.lock.yml | 1 + .../daily-safe-output-integrator.lock.yml | 1 + .../daily-safeoutputs-git-simulator.lock.yml | 1 + .../workflows/daily-workflow-updater.lock.yml | 1 + .github/workflows/dead-code-remover.lock.yml | 1 + .github/workflows/dependabot-repair.lock.yml | 1 + .github/workflows/dependabot-worker.lock.yml | 1 + .../workflows/design-decision-gate.lock.yml | 1 + .../developer-docs-consolidator.lock.yml | 1 + .github/workflows/dictation-prompt.lock.yml | 1 + .../workflows/functional-pragmatist.lock.yml | 1 + .../github-mcp-tools-report.lock.yml | 1 + .../workflows/glossary-maintainer.lock.yml | 1 + .github/workflows/go-logger.lock.yml | 1 + .github/workflows/hourly-ci-cleaner.lock.yml | 1 + .../workflows/instructions-janitor.lock.yml | 1 + .github/workflows/jsweep.lock.yml | 1 + .../workflows/layout-spec-maintainer.lock.yml | 1 + .github/workflows/linter-miner.lock.yml | 1 + .github/workflows/mergefest.lock.yml | 1 + .github/workflows/necromancer.lock.yml | 1 + .github/workflows/pr-sous-chef.lock.yml | 1 + .github/workflows/q.lock.yml | 1 + .github/workflows/refiner.lock.yml | 1 + .github/workflows/ruflo-backed-task.lock.yml | 1 + .../schema-feature-coverage.lock.yml | 1 + .../workflows/slide-deck-maintainer.lock.yml | 1 + .../smoke-create-cross-repo-pr.lock.yml | 1 + .github/workflows/smoke-multi-pr.lock.yml | 1 + .github/workflows/smoke-project.lock.yml | 1 + .../smoke-update-cross-repo-pr.lock.yml | 1 + .github/workflows/spec-enforcer.lock.yml | 1 + .github/workflows/spec-extractor.lock.yml | 1 + .../workflows/technical-doc-writer.lock.yml | 1 + .../test-create-pr-error-handling.lock.yml | 1 + .github/workflows/tidy.lock.yml | 1 + .../workflows/ubuntu-image-analyzer.lock.yml | 1 + .github/workflows/unbloat-docs.lock.yml | 1 + .github/workflows/update-astro.lock.yml | 1 + .../weekly-blog-post-writer.lock.yml | 1 + .../weekly-editors-health-check.lock.yml | 1 + .../weekly-safe-outputs-spec-review.lock.yml | 1 + .../setup/js/push_to_pull_request_branch.cjs | 20 +++++++++------- pkg/workflow/checkout_manager.go | 17 +++++++++++++ pkg/workflow/checkout_step_generator.go | 24 +++++++++++++++++-- pkg/workflow/compiler_safe_outputs_steps.go | 7 ++++++ 62 files changed, 115 insertions(+), 11 deletions(-) diff --git a/.github/workflows/avenger.lock.yml b/.github/workflows/avenger.lock.yml index fcdf4523bef..6e34dfeee17 100644 --- a/.github/workflows/avenger.lock.yml +++ b/.github/workflows/avenger.lock.yml @@ -1802,6 +1802,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 404c65d64e2..66733343251 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -1497,6 +1497,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: diff --git a/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml b/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml index a77f5976975..19235a9e217 100644 --- a/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml +++ b/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml @@ -1672,6 +1672,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml index f4ce0dfab9c..bd5f8403bc8 100644 --- a/.github/workflows/ci-coach.lock.yml +++ b/.github/workflows/ci-coach.lock.yml @@ -1830,6 +1830,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 2c67ca8a9be..46bac049981 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -2112,6 +2112,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/code-scanning-fixer.lock.yml b/.github/workflows/code-scanning-fixer.lock.yml index c00c3e4a713..3315e8a440d 100644 --- a/.github/workflows/code-scanning-fixer.lock.yml +++ b/.github/workflows/code-scanning-fixer.lock.yml @@ -1879,6 +1879,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/code-simplifier.lock.yml b/.github/workflows/code-simplifier.lock.yml index 5503c62a291..ebbbe4fc057 100644 --- a/.github/workflows/code-simplifier.lock.yml +++ b/.github/workflows/code-simplifier.lock.yml @@ -1749,6 +1749,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 14ecdd07606..a4e0998a0c0 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -1772,6 +1772,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: diff --git a/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml b/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml index 209925fbf7f..f8269aaab0c 100644 --- a/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml +++ b/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml @@ -1981,6 +1981,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/daily-architecture-diagram.lock.yml b/.github/workflows/daily-architecture-diagram.lock.yml index 07a86a5196d..9ee0fdcff54 100644 --- a/.github/workflows/daily-architecture-diagram.lock.yml +++ b/.github/workflows/daily-architecture-diagram.lock.yml @@ -1868,6 +1868,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml b/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml index 8438032254b..5cc38f96bf4 100644 --- a/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml +++ b/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml @@ -1819,6 +1819,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/daily-caveman-optimizer.lock.yml b/.github/workflows/daily-caveman-optimizer.lock.yml index 92e75544dc2..d2477c8d23f 100644 --- a/.github/workflows/daily-caveman-optimizer.lock.yml +++ b/.github/workflows/daily-caveman-optimizer.lock.yml @@ -1861,6 +1861,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/daily-community-attribution.lock.yml b/.github/workflows/daily-community-attribution.lock.yml index eaacb4cc119..a27f3606d4b 100644 --- a/.github/workflows/daily-community-attribution.lock.yml +++ b/.github/workflows/daily-community-attribution.lock.yml @@ -1966,6 +1966,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml b/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml index 7d0c95037dd..9678b3b539d 100644 --- a/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml +++ b/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml @@ -1703,6 +1703,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/daily-doc-healer.lock.yml b/.github/workflows/daily-doc-healer.lock.yml index e325fb4fe55..ee79230a062 100644 --- a/.github/workflows/daily-doc-healer.lock.yml +++ b/.github/workflows/daily-doc-healer.lock.yml @@ -1970,6 +1970,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index cb826165360..afd08fda147 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -1896,6 +1896,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/daily-rendering-scripts-verifier.lock.yml b/.github/workflows/daily-rendering-scripts-verifier.lock.yml index 9413e3b47bf..fe241aeda21 100644 --- a/.github/workflows/daily-rendering-scripts-verifier.lock.yml +++ b/.github/workflows/daily-rendering-scripts-verifier.lock.yml @@ -1975,6 +1975,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/daily-safe-output-integrator.lock.yml b/.github/workflows/daily-safe-output-integrator.lock.yml index a9ad42d432d..2a3edf1822a 100644 --- a/.github/workflows/daily-safe-output-integrator.lock.yml +++ b/.github/workflows/daily-safe-output-integrator.lock.yml @@ -1703,6 +1703,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml index ee63b376ec2..004cdc75359 100644 --- a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml +++ b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml @@ -1865,6 +1865,7 @@ jobs: with: persist-credentials: true fetch-depth: 0 + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Fetch additional refs if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: diff --git a/.github/workflows/daily-workflow-updater.lock.yml b/.github/workflows/daily-workflow-updater.lock.yml index ceb78c06617..c0cd7732f0a 100644 --- a/.github/workflows/daily-workflow-updater.lock.yml +++ b/.github/workflows/daily-workflow-updater.lock.yml @@ -1629,6 +1629,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/dead-code-remover.lock.yml b/.github/workflows/dead-code-remover.lock.yml index 87909efca99..c0f5ff22d29 100644 --- a/.github/workflows/dead-code-remover.lock.yml +++ b/.github/workflows/dead-code-remover.lock.yml @@ -1754,6 +1754,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/dependabot-repair.lock.yml b/.github/workflows/dependabot-repair.lock.yml index 67df78e5279..429f82978dc 100644 --- a/.github/workflows/dependabot-repair.lock.yml +++ b/.github/workflows/dependabot-repair.lock.yml @@ -1779,6 +1779,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/dependabot-worker.lock.yml b/.github/workflows/dependabot-worker.lock.yml index ebb433c59a7..20fdaa0aa04 100644 --- a/.github/workflows/dependabot-worker.lock.yml +++ b/.github/workflows/dependabot-worker.lock.yml @@ -1819,6 +1819,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/design-decision-gate.lock.yml b/.github/workflows/design-decision-gate.lock.yml index d4c6c91919a..7e7b82507f0 100644 --- a/.github/workflows/design-decision-gate.lock.yml +++ b/.github/workflows/design-decision-gate.lock.yml @@ -1903,6 +1903,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index a29543424a4..9a08888866f 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -1976,6 +1976,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index 8b17eef8bf1..7b4909782e5 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -1625,6 +1625,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/functional-pragmatist.lock.yml b/.github/workflows/functional-pragmatist.lock.yml index d7f70593d41..d45408b7577 100644 --- a/.github/workflows/functional-pragmatist.lock.yml +++ b/.github/workflows/functional-pragmatist.lock.yml @@ -1636,6 +1636,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index a12a3416815..300201420af 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -1779,6 +1779,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index b7458d144ed..745e734483a 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -1872,6 +1872,7 @@ jobs: with: persist-credentials: true fetch-depth: 0 + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index a61f92ec0ce..1b52a6c1ae2 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -1792,6 +1792,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/hourly-ci-cleaner.lock.yml b/.github/workflows/hourly-ci-cleaner.lock.yml index a7a6bf56c52..3c9960c1392 100644 --- a/.github/workflows/hourly-ci-cleaner.lock.yml +++ b/.github/workflows/hourly-ci-cleaner.lock.yml @@ -1797,6 +1797,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index 5bdd3c546c7..3089d2e067e 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -1767,6 +1767,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/jsweep.lock.yml b/.github/workflows/jsweep.lock.yml index 03382bcbe1b..0a5e1e0c8e9 100644 --- a/.github/workflows/jsweep.lock.yml +++ b/.github/workflows/jsweep.lock.yml @@ -1691,6 +1691,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/layout-spec-maintainer.lock.yml b/.github/workflows/layout-spec-maintainer.lock.yml index 03593b61521..aaeed95c976 100644 --- a/.github/workflows/layout-spec-maintainer.lock.yml +++ b/.github/workflows/layout-spec-maintainer.lock.yml @@ -1676,6 +1676,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/linter-miner.lock.yml b/.github/workflows/linter-miner.lock.yml index ed5b75a2c6c..a58c936d425 100644 --- a/.github/workflows/linter-miner.lock.yml +++ b/.github/workflows/linter-miner.lock.yml @@ -1721,6 +1721,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index e1cf1382a75..872bde55c56 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -1783,6 +1783,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: diff --git a/.github/workflows/necromancer.lock.yml b/.github/workflows/necromancer.lock.yml index 92c93ed9521..527ef3513ac 100644 --- a/.github/workflows/necromancer.lock.yml +++ b/.github/workflows/necromancer.lock.yml @@ -1858,6 +1858,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: diff --git a/.github/workflows/pr-sous-chef.lock.yml b/.github/workflows/pr-sous-chef.lock.yml index bfc3b1a307f..1863972386a 100644 --- a/.github/workflows/pr-sous-chef.lock.yml +++ b/.github/workflows/pr-sous-chef.lock.yml @@ -1812,6 +1812,7 @@ jobs: with: persist-credentials: true fetch-depth: 0 + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Fetch additional refs if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 6b5df5ba591..f86fe2551f8 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -1935,6 +1935,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/refiner.lock.yml b/.github/workflows/refiner.lock.yml index 90466d0214f..2c98838158c 100644 --- a/.github/workflows/refiner.lock.yml +++ b/.github/workflows/refiner.lock.yml @@ -1803,6 +1803,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/ruflo-backed-task.lock.yml b/.github/workflows/ruflo-backed-task.lock.yml index 9153626e06b..132957c5514 100644 --- a/.github/workflows/ruflo-backed-task.lock.yml +++ b/.github/workflows/ruflo-backed-task.lock.yml @@ -1902,6 +1902,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/schema-feature-coverage.lock.yml b/.github/workflows/schema-feature-coverage.lock.yml index b3b3b97803f..b7cf02a1563 100644 --- a/.github/workflows/schema-feature-coverage.lock.yml +++ b/.github/workflows/schema-feature-coverage.lock.yml @@ -1743,6 +1743,7 @@ jobs: with: persist-credentials: true fetch-depth: 1 + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/slide-deck-maintainer.lock.yml b/.github/workflows/slide-deck-maintainer.lock.yml index 10781237215..30e129b0b54 100644 --- a/.github/workflows/slide-deck-maintainer.lock.yml +++ b/.github/workflows/slide-deck-maintainer.lock.yml @@ -1827,6 +1827,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/smoke-create-cross-repo-pr.lock.yml b/.github/workflows/smoke-create-cross-repo-pr.lock.yml index befc298171d..fefdbdc3ee4 100644 --- a/.github/workflows/smoke-create-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-create-cross-repo-pr.lock.yml @@ -1876,6 +1876,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_SIDE_REPO_PAT }} - name: Checkout github/gh-aw-side-repo if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 diff --git a/.github/workflows/smoke-multi-pr.lock.yml b/.github/workflows/smoke-multi-pr.lock.yml index 35a5803eb64..e27e121a71d 100644 --- a/.github/workflows/smoke-multi-pr.lock.yml +++ b/.github/workflows/smoke-multi-pr.lock.yml @@ -1818,6 +1818,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/smoke-project.lock.yml b/.github/workflows/smoke-project.lock.yml index b399a127e0e..9561727c7bd 100644 --- a/.github/workflows/smoke-project.lock.yml +++ b/.github/workflows/smoke-project.lock.yml @@ -2079,6 +2079,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index ce0c2fa853a..00b7d358d6b 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -1910,6 +1910,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_SIDE_REPO_PAT }} - name: Checkout github/gh-aw-side-repo if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 diff --git a/.github/workflows/spec-enforcer.lock.yml b/.github/workflows/spec-enforcer.lock.yml index 2de4a56ba4d..e4ff3900228 100644 --- a/.github/workflows/spec-enforcer.lock.yml +++ b/.github/workflows/spec-enforcer.lock.yml @@ -1804,6 +1804,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/spec-extractor.lock.yml b/.github/workflows/spec-extractor.lock.yml index c4a7a0636b8..022278d4d35 100644 --- a/.github/workflows/spec-extractor.lock.yml +++ b/.github/workflows/spec-extractor.lock.yml @@ -1755,6 +1755,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 89bb0848974..384e6af1bca 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -1876,6 +1876,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/test-create-pr-error-handling.lock.yml b/.github/workflows/test-create-pr-error-handling.lock.yml index 6462015fd7b..c8975882cab 100644 --- a/.github/workflows/test-create-pr-error-handling.lock.yml +++ b/.github/workflows/test-create-pr-error-handling.lock.yml @@ -1735,6 +1735,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 9259ed5e88b..a973cf52031 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -1829,6 +1829,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: diff --git a/.github/workflows/ubuntu-image-analyzer.lock.yml b/.github/workflows/ubuntu-image-analyzer.lock.yml index 3c55106b158..68c84e7749a 100644 --- a/.github/workflows/ubuntu-image-analyzer.lock.yml +++ b/.github/workflows/ubuntu-image-analyzer.lock.yml @@ -1727,6 +1727,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 688f4c78695..b488f20d438 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -1974,6 +1974,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/update-astro.lock.yml b/.github/workflows/update-astro.lock.yml index e28c50cc0f4..291c706c347 100644 --- a/.github/workflows/update-astro.lock.yml +++ b/.github/workflows/update-astro.lock.yml @@ -1752,6 +1752,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/weekly-blog-post-writer.lock.yml b/.github/workflows/weekly-blog-post-writer.lock.yml index 5a5a1ea0ab0..af1fa07b4d0 100644 --- a/.github/workflows/weekly-blog-post-writer.lock.yml +++ b/.github/workflows/weekly-blog-post-writer.lock.yml @@ -2007,6 +2007,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/weekly-editors-health-check.lock.yml b/.github/workflows/weekly-editors-health-check.lock.yml index 0dde4e42000..67519a85c60 100644 --- a/.github/workflows/weekly-editors-health-check.lock.yml +++ b/.github/workflows/weekly-editors-health-check.lock.yml @@ -1705,6 +1705,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/.github/workflows/weekly-safe-outputs-spec-review.lock.yml b/.github/workflows/weekly-safe-outputs-spec-review.lock.yml index a225eba4fd1..a1442ad786c 100644 --- a/.github/workflows/weekly-safe-outputs-spec-review.lock.yml +++ b/.github/workflows/weekly-safe-outputs-spec-review.lock.yml @@ -1628,6 +1628,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: true + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index 1e5f0278461..9708860df9f 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -17,7 +17,7 @@ const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { checkFileProtection, checkFileProtectionPostApply } = require("./manifest_file_helpers.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); const { renderTemplateFromFile, buildProtectedFileList, getPromptPath } = require("./messages_core.cjs"); -const { ensureFullHistoryForBundle, getGitAuthEnv, extractBundlePrerequisiteCommits, isShallowOrSparseCheckout, linearizeRangeAsCommit } = require("./git_helpers.cjs"); +const { ensureFullHistoryForBundle, extractBundlePrerequisiteCommits, isShallowOrSparseCheckout, linearizeRangeAsCommit } = require("./git_helpers.cjs"); const { normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); const { findRepoCheckout } = require("./find_repo_checkout.cjs"); const { getThreatDetectedMarker } = require("./threat_detection_warning.cjs"); @@ -127,12 +127,14 @@ async function main(config = {}) { const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config); const githubClient = await createAuthenticatedGitHubClient(config); - // Build git auth env once for all network operations in this handler. - // clean_git_credentials.sh removes credentials from .git/config before the - // agent runs, so git fetch/push must authenticate via GIT_CONFIG_* env vars. - // Use the per-handler github-token (for cross-repo PAT) when available, - // falling back to GITHUB_TOKEN for the default workflow token. - const gitAuthEnv = getGitAuthEnv(config["github-token"]); + // Git network operations authenticate using the credentials actions/checkout + // persisted into .git/config for the safe_outputs job (persist-credentials: true, + // using the resolved push token). We intentionally do NOT inject an additional + // http.extraheader via GIT_CONFIG_* here: doing so duplicates the Authorization + // header already present in .git/config and causes the server to reject the request + // with "Duplicate header: Authorization" (HTTP 400) on git fetch/push. GitHub API + // ("gh") operations authenticate separately via the authenticated Octokit client above. + const gitAuthEnv = {}; // Base branch from config (if set) - used only for logging at factory level // Dynamic base branch resolution happens per-message after resolving the actual target repo @@ -641,8 +643,8 @@ async function main(config = {}) { } // Fetch the specific target branch from origin - // Use GIT_CONFIG_* env vars for auth because .git/config credentials are - // cleaned by clean_git_credentials.sh before the agent runs. + // Authenticate using the credentials actions/checkout persisted into .git/config + // for the safe_outputs job; no GIT_CONFIG_* extraheader is injected (see gitAuthEnv above). try { core.info(`Fetching branch: ${branchName}`); await exec.exec("git", ["fetch", "origin", `${branchName}:refs/remotes/origin/${branchName}`], { diff --git a/pkg/workflow/checkout_manager.go b/pkg/workflow/checkout_manager.go index 5e6de7e6d59..605680f234d 100644 --- a/pkg/workflow/checkout_manager.go +++ b/pkg/workflow/checkout_manager.go @@ -170,6 +170,14 @@ type CheckoutManager struct { // The agent job leaves this false: the untrusted agent must not be able to read // credentials from disk, so its checkouts use persist-credentials: false. keepCredentialsForPush bool + // pushToken is the token expression persisted into .git/config by every generated + // checkout step when keepCredentialsForPush is enabled and the checkout entry does + // not carry its own explicit token/app auth. Setting this ensures the credential + // retained on disk matches the token the safe_outputs handlers use to push, so a + // single (correct) Authorization header is sent and no separate per-command + // http.extraheader injection is required. Empty means "fall back to the + // actions/checkout default token". + pushToken string } // NewCheckoutManager creates a new CheckoutManager pre-loaded with user-supplied @@ -229,6 +237,15 @@ func (cm *CheckoutManager) SetKeepCredentialsForPush(keep bool) { cm.keepCredentialsForPush = keep } +// SetPushToken sets the token expression persisted into .git/config by the generated +// checkout steps when keepCredentialsForPush is enabled. Call this for the safe_outputs +// job with the resolved PR push token so the retained credential matches the token the +// handlers use to fetch/push. Has no effect on entries that declare their own token/app. +func (cm *CheckoutManager) SetPushToken(token string) { + checkoutManagerLog.Printf("Setting pushToken: present=%t", token != "") + cm.pushToken = token +} + // add processes a single CheckoutConfig and either creates a new entry or merges // it into an existing entry with the same key. func (cm *CheckoutManager) add(cfg *CheckoutConfig) { diff --git a/pkg/workflow/checkout_step_generator.go b/pkg/workflow/checkout_step_generator.go index 8c6e436d418..fa9c3c66c5b 100644 --- a/pkg/workflow/checkout_step_generator.go +++ b/pkg/workflow/checkout_step_generator.go @@ -61,7 +61,7 @@ func (cm *CheckoutManager) GenerateAdditionalCheckoutSteps(getActionPin func(str if entry.key.path == "" && entry.key.repository == "" { continue } - lines = append(lines, generateCheckoutStepLines(entry, checkoutIndex, cm.keepCredentialsForPush, getActionPin)...) + lines = append(lines, generateCheckoutStepLines(entry, checkoutIndex, cm.keepCredentialsForPush, cm.pushToken, getActionPin)...) } checkoutManagerLog.Printf("Generated %d additional checkout step(s)", len(lines)) return lines @@ -302,6 +302,10 @@ func (cm *CheckoutManager) GenerateDefaultCheckoutStep( sb.WriteString(" persist-credentials: false\n") } + // Track whether a token has been written to the checkout step so the safe_outputs + // push-token fallback below does not double-emit. + tokenEmitted := false + // Apply trial mode overrides if trialMode { if trialLogicalRepoSlug != "" { @@ -309,6 +313,7 @@ func (cm *CheckoutManager) GenerateDefaultCheckoutStep( } effectiveToken := getEffectiveGitHubToken("") fmt.Fprintf(&sb, " token: %s\n", effectiveToken) + tokenEmitted = true } // Apply user overrides (only when NOT in trial mode to avoid conflicts) @@ -349,6 +354,7 @@ func (cm *CheckoutManager) GenerateDefaultCheckoutStep( } if effectiveOverrideToken != "" { fmt.Fprintf(&sb, " token: %s\n", effectiveOverrideToken) + tokenEmitted = true } if override.fetchDepth != nil { fmt.Fprintf(&sb, " fetch-depth: %d\n", *override.fetchDepth) @@ -367,6 +373,14 @@ func (cm *CheckoutManager) GenerateDefaultCheckoutStep( } } + // safe_outputs job: when no explicit token was written above, persist the resolved + // push token so the credential retained in .git/config matches the token the + // safe-output handlers use to fetch/push (avoiding both a wrong-token push and the + // duplicate Authorization header that a separate per-command extraheader would add). + if !trialMode && !tokenEmitted && cm.keepCredentialsForPush && cm.pushToken != "" { + fmt.Fprintf(&sb, " token: %s\n", cm.pushToken) + } + steps := []string{sb.String()} if override != nil && len(override.sparsePatterns) > 0 { steps = append(steps, generateSparseCheckoutPartialCloneResetStep("")) @@ -397,7 +411,7 @@ func (cm *CheckoutManager) GenerateDefaultCheckoutStep( // When keepCredentialsForPush is true (safe_outputs job), credentials are retained // (persist-credentials: true) and the post-checkout cleanup step is suppressed so a later // git fetch/push can authenticate. -func generateCheckoutStepLines(entry *resolvedCheckout, index int, keepCredentialsForPush bool, getActionPin func(string) string) []string { +func generateCheckoutStepLines(entry *resolvedCheckout, index int, keepCredentialsForPush bool, pushToken string, getActionPin func(string) string) []string { checkoutManagerLog.Printf("Generating checkout step lines: index=%d, repo=%q, path=%q, ref=%q, appAuth=%v", index, entry.key.repository, entry.key.path, entry.ref, entry.githubApp != nil) name := "Checkout " + checkoutStepName(entry.key) @@ -430,6 +444,12 @@ func generateCheckoutStepLines(entry *resolvedCheckout, index int, keepCredentia } // Determine effective token: github-app-minted token takes precedence effectiveToken := resolveCheckoutTokenExpression(entry, index, false) + // safe_outputs job: when this checkout declares no token/app of its own, persist the + // resolved push token so the retained .git/config credential matches the token the + // safe-output handlers use to fetch/push. + if effectiveToken == "" && keepCredentialsForPush && pushToken != "" { + effectiveToken = pushToken + } if effectiveToken != "" { fmt.Fprintf(&sb, " token: %s\n", effectiveToken) } diff --git a/pkg/workflow/compiler_safe_outputs_steps.go b/pkg/workflow/compiler_safe_outputs_steps.go index db5db02fb4e..6965dea41f4 100644 --- a/pkg/workflow/compiler_safe_outputs_steps.go +++ b/pkg/workflow/compiler_safe_outputs_steps.go @@ -38,6 +38,13 @@ func (c *Compiler) buildSharedPRCheckoutSteps(data *WorkflowData) []string { // safe_outputs handler code (not the untrusted agent) is the only consumer. checkoutMgr.SetKeepCredentialsForPush(true) + // Persist the resolved PR push token (not just the default GITHUB_TOKEN) into + // .git/config so the retained credential matches the token the handlers use to + // fetch/push. This keeps a single, correct Authorization header on the wire and + // removes the need for the handlers to inject a separate http.extraheader. + pushToken, _ := resolvePRCheckoutToken(data.SafeOutputs) + checkoutMgr.SetPushToken(pushToken) + // Combined condition: run the checkout/git-config steps only when a create_pull_request // or push_to_pull_request_branch output will be processed. condition := buildPRCheckoutCondition(data.SafeOutputs) From fc93a951602f0879b1e961f817573d107463bc44 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Fri, 19 Jun 2026 13:56:45 +0100 Subject: [PATCH 2/3] Address PR review: dedupe token resolve, add fallback tests, doc note - compiler_safe_outputs_steps.go: resolve the PR checkout token once and reuse it for both the persisted-checkout push token and the Configure Git Credentials step (was computed twice). - checkout_manager_test.go: add TestCheckoutPushTokenFallback covering the safe_outputs push-token fallback (default + additional checkouts): emits the push token exactly once when no explicit token, does not override an explicit or app-minted token, and is suppressed when credentials are not retained. - docs: document which single token governs the shared PR checkout for git operations and recommend using the same token when both create-pull-request and push-to-pull-request-branch target the same repository. --- .../reference/safe-outputs-pull-requests.md | 16 ++++ pkg/workflow/checkout_manager_test.go | 79 +++++++++++++++++++ pkg/workflow/compiler_safe_outputs_steps.go | 14 ++-- 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/docs/src/content/docs/reference/safe-outputs-pull-requests.md b/docs/src/content/docs/reference/safe-outputs-pull-requests.md index 5bc695a693a..45998017d27 100644 --- a/docs/src/content/docs/reference/safe-outputs-pull-requests.md +++ b/docs/src/content/docs/reference/safe-outputs-pull-requests.md @@ -353,6 +353,22 @@ See [Cross-Repository Operations](/gh-aw/reference/cross-repository/) for a comp Like `create-pull-request`, pushes with GitHub Agentic Workflows do not trigger CI. See [Triggering CI](/gh-aw/reference/triggering-ci/) for how to enable automatic CI triggers. +### Checkout token for git operations + +`create-pull-request` and `push-to-pull-request-branch` run their git operations (fetch/push) against a repository that the `safe_outputs` job checks out with credentials persisted in `.git/config`. A **single** token is persisted into that checkout, resolved with this precedence: + +1. `create-pull-request.github-token` +2. `push-to-pull-request-branch.github-token` +3. The `safe-outputs.github-app` minted token (when a GitHub App is configured) +4. `safe-outputs.github-token` +5. The default `${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}` + +Because only one token can govern the shared checkout, **if you configure both `create-pull-request` and `push-to-pull-request-branch` for the same repository, give them the same token.** If they specify different `github-token` values, the higher-precedence one wins for the checkout, so the other output's git operations run with a token you did not intend. Set the token once at `safe-outputs.github-token` (or `safe-outputs.github-app`) and let both outputs inherit it, or set identical `github-token` values on each. + +:::note +This applies to the git checkout used by the handlers' `fetch`/`push`. The GitHub API calls each handler makes still honor that handler's own `github-token` precedence. +::: + ## Add Reviewer (`add-reviewer:`) Adds reviewers to pull requests. Specify `allowed-reviewers` to restrict to specific GitHub usernames and `allowed-team-reviewers` to restrict to specific team slugs. diff --git a/pkg/workflow/checkout_manager_test.go b/pkg/workflow/checkout_manager_test.go index 9ab8c22d4fe..8719ec84463 100644 --- a/pkg/workflow/checkout_manager_test.go +++ b/pkg/workflow/checkout_manager_test.go @@ -234,6 +234,85 @@ func TestGenerateDefaultCheckoutStep(t *testing.T) { }) } +// TestCheckoutPushTokenFallback verifies the safe_outputs push-token fallback that +// persists the resolved PR push token into the checkout when keepCredentialsForPush is +// enabled and no explicit checkout token (or app auth) already governs the checkout. +func TestCheckoutPushTokenFallback(t *testing.T) { + getPin := func(action string) string { return action + "@v4" } + const pushToken = "${{ secrets.PUSH_TOKEN }}" + + t.Run("default checkout with no explicit token emits pushToken once", func(t *testing.T) { + cm := NewCheckoutManager(nil) + cm.SetKeepCredentialsForPush(true) + cm.SetPushToken(pushToken) + lines := cm.GenerateDefaultCheckoutStep(false, "", getPin) + combined := strings.Join(lines, "") + assert.Contains(t, combined, "persist-credentials: true", "keepCredentialsForPush should retain credentials") + assert.Contains(t, combined, "token: "+pushToken, "should persist the push token") + assert.Equal(t, 1, strings.Count(combined, "token: "), "token must be emitted exactly once") + }) + + t.Run("default checkout with explicit token does not override with pushToken", func(t *testing.T) { + cm := NewCheckoutManager([]*CheckoutConfig{ + {GitHubToken: "${{ secrets.MY_TOKEN }}"}, + }) + cm.SetKeepCredentialsForPush(true) + cm.SetPushToken(pushToken) + lines := cm.GenerateDefaultCheckoutStep(false, "", getPin) + combined := strings.Join(lines, "") + assert.Contains(t, combined, "token: ${{ secrets.MY_TOKEN }}", "explicit checkout token should win") + assert.NotContains(t, combined, pushToken, "pushToken must not override an explicit checkout token") + assert.Equal(t, 1, strings.Count(combined, "token: "), "token must be emitted exactly once") + }) + + t.Run("default checkout with app auth does not override with pushToken", func(t *testing.T) { + cm := NewCheckoutManager([]*CheckoutConfig{ + {GitHubApp: &GitHubAppConfig{AppID: "${{ vars.APP_ID }}", PrivateKey: "${{ secrets.APP_KEY }}"}}, + }) + cm.SetKeepCredentialsForPush(true) + cm.SetPushToken(pushToken) + lines := cm.GenerateDefaultCheckoutStep(false, "", getPin) + combined := strings.Join(lines, "") + assert.Contains(t, combined, "checkout-app-token-0.outputs.token", "app-minted token should govern the checkout") + assert.NotContains(t, combined, pushToken, "pushToken must not override an app-minted token") + }) + + t.Run("default checkout does not emit pushToken when keepCredentialsForPush is false", func(t *testing.T) { + cm := NewCheckoutManager(nil) + cm.SetPushToken(pushToken) + lines := cm.GenerateDefaultCheckoutStep(false, "", getPin) + combined := strings.Join(lines, "") + assert.Contains(t, combined, "persist-credentials: false", "agent-style checkout strips credentials") + assert.NotContains(t, combined, pushToken, "pushToken must not be persisted when credentials are not retained") + }) + + t.Run("additional checkout with no token uses pushToken", func(t *testing.T) { + cm := NewCheckoutManager([]*CheckoutConfig{ + {Repository: "owner/libs", Path: "./libs"}, + }) + cm.SetKeepCredentialsForPush(true) + cm.SetPushToken(pushToken) + lines := cm.GenerateAdditionalCheckoutSteps(getPin) + combined := strings.Join(lines, "") + assert.Contains(t, combined, "persist-credentials: true", "keepCredentialsForPush should retain credentials") + assert.Contains(t, combined, "token: "+pushToken, "additional checkout should fall back to the push token") + assert.Equal(t, 1, strings.Count(combined, "token: "), "token must be emitted exactly once") + }) + + t.Run("additional checkout with explicit token does not override with pushToken", func(t *testing.T) { + cm := NewCheckoutManager([]*CheckoutConfig{ + {Repository: "owner/libs", Path: "./libs", GitHubToken: "${{ secrets.MY_TOKEN }}"}, + }) + cm.SetKeepCredentialsForPush(true) + cm.SetPushToken(pushToken) + lines := cm.GenerateAdditionalCheckoutSteps(getPin) + combined := strings.Join(lines, "") + assert.Contains(t, combined, "token: ${{ secrets.MY_TOKEN }}", "explicit checkout token should win") + assert.NotContains(t, combined, pushToken, "pushToken must not override an explicit checkout token") + assert.Equal(t, 1, strings.Count(combined, "token: "), "token must be emitted exactly once") + }) +} + // TestGenerateAdditionalCheckoutSteps verifies that non-default checkouts are emitted correctly. func TestGenerateAdditionalCheckoutSteps(t *testing.T) { getPin := func(action string) string { return action + "@v4" } diff --git a/pkg/workflow/compiler_safe_outputs_steps.go b/pkg/workflow/compiler_safe_outputs_steps.go index 6965dea41f4..cb6f5e49bb6 100644 --- a/pkg/workflow/compiler_safe_outputs_steps.go +++ b/pkg/workflow/compiler_safe_outputs_steps.go @@ -41,9 +41,11 @@ func (c *Compiler) buildSharedPRCheckoutSteps(data *WorkflowData) []string { // Persist the resolved PR push token (not just the default GITHUB_TOKEN) into // .git/config so the retained credential matches the token the handlers use to // fetch/push. This keeps a single, correct Authorization header on the wire and - // removes the need for the handlers to inject a separate http.extraheader. - pushToken, _ := resolvePRCheckoutToken(data.SafeOutputs) - checkoutMgr.SetPushToken(pushToken) + // removes the need for the handlers to inject a separate http.extraheader. The + // same token is reused below for the "Configure Git credentials" step so both the + // persisted checkout credential and the push remote agree on a single token. + prCheckoutToken, _ := resolvePRCheckoutToken(data.SafeOutputs) + checkoutMgr.SetPushToken(prCheckoutToken) // Combined condition: run the checkout/git-config steps only when a create_pull_request // or push_to_pull_request_branch output will be processed. @@ -71,9 +73,9 @@ func (c *Compiler) buildSharedPRCheckoutSteps(data *WorkflowData) []string { )...) // Configure Git credentials so the safe_outputs job can push. The agent job never - // pushes, so this step has no agent-job equivalent. - gitRemoteToken, _ := resolvePRCheckoutToken(data.SafeOutputs) - steps = append(steps, checkoutMgr.GenerateConfigureGitCredentialsSteps(gitRemoteToken, condition)...) + // pushes, so this step has no agent-job equivalent. Reuse the token resolved above so + // the push remote and the persisted checkout credential use the same token. + steps = append(steps, checkoutMgr.GenerateConfigureGitCredentialsSteps(prCheckoutToken, condition)...) consolidatedSafeOutputsStepsLog.Printf("Built shared PR checkout steps with condition: %s", condition.Render()) return steps From 2bb81019033c0c37755b644c3756eb4508025335 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Fri, 19 Jun 2026 15:39:03 +0100 Subject: [PATCH 3/3] Extend duplicate-Authorization fix to dynamic_checkout.cjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the safe_outputs checkout runs with persist-credentials: true (now with the resolved push token via SetPushToken), .git/config already holds an http./.extraheader that authenticates every URL — including the repo dynamic_checkout switches origin to. Injecting a second extraheader put a duplicate Authorization header on the wire (git treats the key as multi-valued), which GitHub rejects with "Duplicate header: 'Authorization'" (HTTP 400). checkoutRepo now trusts the persisted credential and only configures an extraheader when none is already persisted (preserving callers that run without persist-credentials). Repointing origin to a clean URL still drops any embedded-credential URL, leaving the persisted extraheader as the single auth source. Mirrors the push_to_pull_request_branch.cjs fix. Adds tests covering both the persisted (skip injection) and non-persisted (inject) paths. --- actions/setup/js/dynamic_checkout.cjs | 54 ++++++++++++++++--- actions/setup/js/dynamic_checkout.test.cjs | 60 ++++++++++++++++++++++ 2 files changed, 107 insertions(+), 7 deletions(-) diff --git a/actions/setup/js/dynamic_checkout.cjs b/actions/setup/js/dynamic_checkout.cjs index ef47a8fbae5..2587d8fde46 100644 --- a/actions/setup/js/dynamic_checkout.cjs +++ b/actions/setup/js/dynamic_checkout.cjs @@ -46,6 +46,31 @@ async function getCurrentCheckoutRepo() { } } +/** + * Determine whether the current checkout already has a persisted + * http./.extraheader credential in .git/config. + * + * When actions/checkout runs with persist-credentials: true (as the safe_outputs job + * does), it writes an http./.extraheader entry that authenticates every + * URL. In that case a dynamic repo switch must NOT inject a second + * extraheader, because git treats the key as multi-valued and would send two + * Authorization headers, which GitHub rejects with "Duplicate header: 'Authorization'". + * + * @param {string} serverUrl - GitHub server URL (e.g. "https://github.com") + * @returns {Promise} true if a non-empty extraheader is already configured + */ +async function checkoutHasPersistedExtraheader(serverUrl) { + try { + const result = await exec.getExecOutput("git", ["config", "--get-all", `http.${serverUrl}/.extraheader`], { + silent: true, + ignoreReturnCode: true, + }); + return result.exitCode === 0 && result.stdout.trim() !== ""; + } catch { + return false; + } +} + /** * Checkout a different repository for patch application * This is used when processing entries with a `repo` parameter that differs from the current checkout @@ -86,18 +111,33 @@ async function checkoutRepo(repoSlug, token, options = {}) { // Get GitHub server URL (for GHES support) const serverUrl = process.env.GITHUB_SERVER_URL || "https://github.com"; - // Configure the new remote URL - // Use token in URL for authentication since we're not using credential helper + // Configure the new remote URL (no embedded credentials). Authentication is + // provided by the http./.extraheader credential that the safe_outputs + // checkout persisted into .git/config (persist-credentials: true with the resolved + // push token). That extraheader applies to every URL, so it already + // authenticates the repository we are switching to. const remoteUrl = `${serverUrl}/${repoSlug}.git`; - // Change remote origin to the new repo + // Change remote origin to the new repo. Replacing the URL also drops any + // embedded-credential URL configure_git_credentials.sh may have set, leaving the + // persisted extraheader as the single source of auth. core.info(`Configuring remote origin to: ${repoSlug}`); await exec.exec("git", ["remote", "set-url", "origin", remoteUrl]); - // Configure token for authentication - // Use extraheader to pass token without embedding in URL (more secure) - const tokenBase64 = Buffer.from(`x-access-token:${token}`).toString("base64"); - await exec.exec("git", ["config", `http.${serverUrl}/.extraheader`, `Authorization: basic ${tokenBase64}`]); + // Trust the persisted credential rather than injecting a second one. git treats + // http..extraheader as multi-valued, so adding our own header on top of the + // checkout's persisted header would put two Authorization headers on the wire, + // which GitHub rejects with "Duplicate header: 'Authorization'" (HTTP 400). Only + // configure an extraheader when the checkout did not already persist one (e.g. a + // caller that runs without persist-credentials). + const hasPersistedAuth = await checkoutHasPersistedExtraheader(serverUrl); + if (!hasPersistedAuth) { + // Use extraheader to pass the token without embedding it in the URL (more secure). + const tokenBase64 = Buffer.from(`x-access-token:${token}`).toString("base64"); + await exec.exec("git", ["config", `http.${serverUrl}/.extraheader`, `Authorization: basic ${tokenBase64}`]); + } else { + core.info("Reusing persisted git credential for authentication (skipping extraheader injection)"); + } // Fetch the new repo core.info(`Fetching repository: ${repoSlug}`); diff --git a/actions/setup/js/dynamic_checkout.test.cjs b/actions/setup/js/dynamic_checkout.test.cjs index d983058fe3b..8213320a40b 100644 --- a/actions/setup/js/dynamic_checkout.test.cjs +++ b/actions/setup/js/dynamic_checkout.test.cjs @@ -203,6 +203,66 @@ describe("checkoutRepo slug validation", () => { }); }); +describe("checkoutRepo extraheader credential handling", () => { + let mockExec; + let mockCore; + let originalExec; + let originalCore; + + /** Build an exec mock whose `git config --get-all ...extraheader` returns `persisted`. */ + function makeExec(persisted) { + return { + exec: vi.fn().mockResolvedValue(0), + getExecOutput: vi.fn().mockImplementation((_cmd, args) => { + if (args && args[0] === "config" && args.includes("--get-all")) { + return Promise.resolve({ stdout: persisted, stderr: "", exitCode: persisted ? 0 : 1 }); + } + return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 }); + }), + }; + } + + /** Returns true if any exec.exec call configured an http.<...>.extraheader. */ + function injectedExtraheader(exec) { + return exec.exec.mock.calls.some(([, args]) => Array.isArray(args) && args[0] === "config" && args.some(a => typeof a === "string" && a.endsWith(".extraheader"))); + } + + beforeEach(() => { + originalExec = global.exec; + originalCore = global.core; + mockCore = { info: vi.fn(), error: vi.fn(), warning: vi.fn(), debug: vi.fn() }; + global.core = mockCore; + }); + + afterEach(() => { + global.exec = originalExec; + global.core = originalCore; + }); + + it("does not inject a second extraheader when the checkout already persisted one", async () => { + mockExec = makeExec("AUTHORIZATION: basic PERSISTED"); + global.exec = mockExec; + + const result = await checkoutRepo("owner/repo", "fake-token", { baseBranch: "main" }); + + expect(result.success).toBe(true); + expect(injectedExtraheader(mockExec)).toBe(false); + // origin is still repointed at the target repo so the persisted credential applies + const setUrl = mockExec.exec.mock.calls.find(([, args]) => Array.isArray(args) && args[0] === "remote" && args[1] === "set-url"); + expect(setUrl).toBeDefined(); + }); + + it("injects an extraheader when no credential is persisted", async () => { + mockExec = makeExec(""); + global.exec = mockExec; + + const result = await checkoutRepo("owner/repo", "fake-token", { baseBranch: "main" }); + + expect(result.success).toBe(true); + expect(injectedExtraheader(mockExec)).toBe(true); + }); +}); + describe("getCurrentCheckoutRepo URL parsing", () => { let mockCore; let originalExec;