Fix duplicate Authorization header (HTTP 400) on git ops in push_to_pull_request_branch#40281
Conversation
…_branch The safe_outputs job set http.<url>.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
Comment MemoryNote This comment is managed by comment memory.It stores persistent context for this thread in the code block at the top of this comment.
|
There was a problem hiding this comment.
Pull request overview
This pull request fixes intermittent HTTP 400 failures in the safe_outputs job when push_to_pull_request_branch performs git network operations, caused by git emitting duplicate Authorization headers. It does so by removing per-command http.extraheader injection in the JS handler and ensuring the safe_outputs checkout persists exactly one correct credential (the resolved PR push token) into .git/config.
Changes:
- Stop
push_to_pull_request_branchfrom injectingGIT_CONFIG_*auth (eliminates duplicateAuthorizationheaders). - Extend checkout generation to support a
pushTokenused whenkeepCredentialsForPushis enabled, and emit that token intoactions/checkoutso persisted credentials match the handler’s push/fetch token. - Regenerate workflow
.lock.ymlfiles sosafe_outputscheckouts explicitly settoken: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}.
Show a summary per file
| File | Description |
|---|---|
| actions/setup/js/push_to_pull_request_branch.cjs | Removes getGitAuthEnv-based extraheader injection; relies on persisted checkout credentials. |
| pkg/workflow/compiler_safe_outputs_steps.go | Sets CheckoutManager to persist credentials for push and provides the resolved PR push token to checkout generation. |
| pkg/workflow/checkout_manager.go | Adds pushToken field + SetPushToken to carry the resolved PR push token into checkout generation. |
| pkg/workflow/checkout_step_generator.go | Emits actions/checkout token: from pushToken when keepCredentialsForPush is enabled and no per-checkout token/app is set. |
| .github/workflows/avenger.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/chaos-pr-bundle-fuzzer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/changeset.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/ci-coach.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/cloclo.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/code-scanning-fixer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/code-simplifier.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/craft.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-agent-of-the-day-blog-writer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-architecture-diagram.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-caveman-optimizer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-community-attribution.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-compiler-threat-spec-optimizer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-doc-healer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-doc-updater.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-rendering-scripts-verifier.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-safe-output-integrator.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-safeoutputs-git-simulator.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/daily-workflow-updater.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/dead-code-remover.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/dependabot-repair.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/dependabot-worker.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/design-decision-gate.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/developer-docs-consolidator.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/dictation-prompt.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/functional-pragmatist.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/github-mcp-tools-report.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/glossary-maintainer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/go-logger.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/hourly-ci-cleaner.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/instructions-janitor.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/jsweep.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/layout-spec-maintainer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/linter-miner.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/mergefest.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/necromancer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/pr-sous-chef.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/q.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/refiner.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/ruflo-backed-task.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/schema-feature-coverage.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/slide-deck-maintainer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/smoke-create-cross-repo-pr.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials (incl. side repo PAT cases). |
| .github/workflows/smoke-multi-pr.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/smoke-project.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/smoke-update-cross-repo-pr.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials (incl. side repo PAT cases). |
| .github/workflows/spec-enforcer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/spec-extractor.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/technical-doc-writer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/test-create-pr-error-handling.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/tidy.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/ubuntu-image-analyzer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/unbloat-docs.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/update-astro.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/weekly-blog-post-writer.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/weekly-editors-health-check.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
| .github/workflows/weekly-safe-outputs-spec-review.lock.yml | Regenerated: safe_outputs checkout now explicitly sets token for persisted credentials. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 62/62 changed files
- Comments generated: 2
- 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.
When the safe_outputs checkout runs with persist-credentials: true (now with the resolved push token via SetPushToken), .git/config already holds an http.<serverUrl>/.extraheader that authenticates every <serverUrl> 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.
…ader-safe-outputs
…ub.com/github/gh-aw into fix/duplicate-auth-header-safe-outputs
Fix: Duplicate Authorization header on git operations in safe-outputs workflows
Problem
When
safe_outputsworkflows performed git fetch/push operations, two conflictingAuthorizationheaders were being sent:.git/configbyactions/checkoutas anhttp.extraheadergetGitAuthEnv/ a secondhttp.extraheaderGitHub rejected requests with duplicate
Authorizationheaders, causing git operations to fail.Root cause
Two independent code paths were both trying to set credentials for the same checkout:
pkg/workflow) wrote aConfigure Git credentialsstep that could re-inject a token into.git/configusing a different expression than the oneactions/checkouthad already persisted there.actions/setup/js) inpush_to_pull_request_branch.cjscalledgetGitAuthEnvto pass an auth env var togit, anddynamic_checkout.cjscould write a secondhttp.extraheaderon top of the oneactions/checkoutleft behind.Changes
pkg/workflow/checkout_manager.gopushToken stringfield and publicSetPushToken(token string)method.keepCredentialsForPushis enabled, decoupled from checkout-level explicit token/app auth.pkg/workflow/checkout_step_generator.gopushToken stringparameter togenerateCheckoutStepLines; used aseffectiveTokenwhen the checkout carries no explicit token/app auth andkeepCredentialsForPushis true.tokenEmittedguard inGenerateDefaultCheckoutStepso the push-token fallback is only written when no other credential expression was already emitted.cm.pushTokenintogenerateCheckoutStepLinescalls for additional (non-default) checkouts.pkg/workflow/compiler_safe_outputs_steps.goresolvePRCheckoutTokenonce and passes the result tocm.SetPushToken(...), ensuring the persisted.git/configcredential and the "Configure Git credentials" step are derived from the same resolved token, eliminating the mismatch.pkg/workflow/checkout_manager_test.goTestCheckoutPushTokenFallbackwith 6 sub-tests:keepCredentialsForPushis falseactions/setup/js/dynamic_checkout.cjscheckoutHasPersistedExtraheader(repoPath)helper that inspects the repo's.git/configto detect whetheractions/checkoutalready wrote anhttp.extraheader.checkoutRepoto skip injecting a secondhttp.extraheaderwhen the helper returnstrue.actions/setup/js/dynamic_checkout.test.cjsdescribeblock with 2 test cases:actions/setup/js/push_to_pull_request_branch.cjsgetGitAuthEnvimport and replaces thegitAuthEnv-populated object with an empty object{}..git/configby thesafe_outputscheckout step.docs/src/content/docs/reference/safe-outputs-pull-requests.mdsafe_outputscheckout.create-pull-requestandpush-to-pull-request-branchwhen both target the same repository.Impact summary
checkout_manager.go,checkout_step_generator.go,compiler_safe_outputs_steps.godynamic_checkout.cjs,push_to_pull_request_branch.cjscheckout_manager_test.go,dynamic_checkout.test.cjssafe-outputs-pull-requests.mdsafe_outputsworkflows no longer inject a second credential on top of the one persisted byactions/checkout.resolvePRCheckoutToken) and shared across both the persisted.git/configcredential and the runtime credential steps.Key invariants enforced after this fix
actions/checkoutpersisted anhttp.extraheader,dynamic_checkout.cjswill not add another.push_to_pull_request_branch.cjspasses no auth env vars togit; it relies solely on the persisted.git/configentry.pushTokenfallback.pushTokenfallback is only emitted whenkeepCredentialsForPushistrueand no other credential was already emitted (tokenEmitted == false).