Skip to content

Fix duplicate Authorization header (HTTP 400) on git ops in push_to_pull_request_branch#40281

Merged
dsyme merged 7 commits into
mainfrom
fix/duplicate-auth-header-safe-outputs
Jun 19, 2026
Merged

Fix duplicate Authorization header (HTTP 400) on git ops in push_to_pull_request_branch#40281
dsyme merged 7 commits into
mainfrom
fix/duplicate-auth-header-safe-outputs

Conversation

@dsyme

@dsyme dsyme commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Fix: Duplicate Authorization header on git operations in safe-outputs workflows

Problem

When safe_outputs workflows performed git fetch/push operations, two conflicting Authorization headers were being sent:

  1. One persisted in .git/config by actions/checkout as an http.extraheader
  2. One injected at runtime via getGitAuthEnv / a second http.extraheader

GitHub rejected requests with duplicate Authorization headers, causing git operations to fail.


Root cause

Two independent code paths were both trying to set credentials for the same checkout:

  • The Go compiler (pkg/workflow) wrote a Configure Git credentials step that could re-inject a token into .git/config using a different expression than the one actions/checkout had already persisted there.
  • The JS runtime layer (actions/setup/js) in push_to_pull_request_branch.cjs called getGitAuthEnv to pass an auth env var to git, and dynamic_checkout.cjs could write a second http.extraheader on top of the one actions/checkout left behind.

Changes

pkg/workflow/checkout_manager.go

  • Added pushToken string field and public SetPushToken(token string) method.
  • Stores the token expression to be used when keepCredentialsForPush is enabled, decoupled from checkout-level explicit token/app auth.

pkg/workflow/checkout_step_generator.go

  • Added pushToken string parameter to generateCheckoutStepLines; used as effectiveToken when the checkout carries no explicit token/app auth and keepCredentialsForPush is true.
  • Added a tokenEmitted guard in GenerateDefaultCheckoutStep so the push-token fallback is only written when no other credential expression was already emitted.
  • Threads cm.pushToken into generateCheckoutStepLines calls for additional (non-default) checkouts.

pkg/workflow/compiler_safe_outputs_steps.go

  • Calls resolvePRCheckoutToken once and passes the result to cm.SetPushToken(...), ensuring the persisted .git/config credential and the "Configure Git credentials" step are derived from the same resolved token, eliminating the mismatch.

pkg/workflow/checkout_manager_test.go

  • Adds TestCheckoutPushTokenFallback with 6 sub-tests:
    • Default and additional checkout paths
    • Explicit token takes precedence over push-token fallback
    • App auth takes precedence over push-token fallback
    • Fallback is suppressed when keepCredentialsForPush is false

actions/setup/js/dynamic_checkout.cjs

  • Adds checkoutHasPersistedExtraheader(repoPath) helper that inspects the repo's .git/config to detect whether actions/checkout already wrote an http.extraheader.
  • Updates checkoutRepo to skip injecting a second http.extraheader when the helper returns true.

actions/setup/js/dynamic_checkout.test.cjs

  • New describe block with 2 test cases:
    • Asserts no second header is injected when a persisted credential exists.
    • Asserts injection proceeds normally when no persisted credential is present.

actions/setup/js/push_to_pull_request_branch.cjs

  • Removes the getGitAuthEnv import and replaces the gitAuthEnv-populated object with an empty object {}.
  • Git fetch/push authentication now relies entirely on the credential already persisted in .git/config by the safe_outputs checkout step.

docs/src/content/docs/reference/safe-outputs-pull-requests.md

  • Adds a new "Checkout token for git operations" section documenting token-precedence rules for the shared safe_outputs checkout.
  • Advises users to use the same token for create-pull-request and push-to-pull-request-branch when both target the same repository.

Impact summary

Layer Files changed Risk
Go compiler (workflow generation) checkout_manager.go, checkout_step_generator.go, compiler_safe_outputs_steps.go High — changes credential emission logic for all safe-outputs checkouts
JS runtime (action scripts) dynamic_checkout.cjs, push_to_pull_request_branch.cjs High — changes how git auth env/extraheader is set during push
Tests checkout_manager_test.go, dynamic_checkout.test.cjs Low — additive coverage only
Docs safe-outputs-pull-requests.md Low — documentation only
  • Breaking change: No
  • Behaviour change: Git operations in safe_outputs workflows no longer inject a second credential on top of the one persisted by actions/checkout.
  • Token resolution: Token is now resolved once (in resolvePRCheckoutToken) and shared across both the persisted .git/config credential and the runtime credential steps.

Key invariants enforced after this fix

  1. If actions/checkout persisted an http.extraheader, dynamic_checkout.cjs will not add another.
  2. push_to_pull_request_branch.cjs passes no auth env vars to git; it relies solely on the persisted .git/config entry.
  3. Explicit token and app auth on a checkout always take precedence over the pushToken fallback.
  4. The pushToken fallback is only emitted when keepCredentialsForPush is true and no other credential was already emitted (tokenEmitted == false).

Generated by PR Description Updater for issue #40281 · 144.9 AIC · ⌖ 7.04 AIC · ⊞ 4.5K ·

…_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
Copilot AI review requested due to automatic review settings June 19, 2026 10:36
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Comment Memory

CI lights the path
Green checks bloom at dawn
Quiet bots still sing

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

Generated by 🧪 Smoke CI for issue #40281 ·

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

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_branch from injecting GIT_CONFIG_* auth (eliminates duplicate Authorization headers).
  • Extend checkout generation to support a pushToken used when keepCredentialsForPush is enabled, and emit that token into actions/checkout so persisted credentials match the handler’s push/fetch token.
  • Regenerate workflow .lock.yml files so safe_outputs checkouts explicitly set token: ${{ 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

Comment thread pkg/workflow/checkout_step_generator.go
Comment thread pkg/workflow/compiler_safe_outputs_steps.go Outdated
dsyme and others added 4 commits June 19, 2026 13:56
- 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.
Ahamed091

This comment was marked as spam.

@dsyme dsyme merged commit 2a5482d into main Jun 19, 2026
37 checks passed
@dsyme dsyme deleted the fix/duplicate-auth-header-safe-outputs branch June 19, 2026 16:23
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