Skip to content

fix: don't configure the external-auth URL as a Composer GitHub token#180

Merged
rfay merged 1 commit into
mainfrom
fix-composer-github-auth
Jul 25, 2026
Merged

fix: don't configure the external-auth URL as a Composer GitHub token#180
rfay merged 1 commit into
mainfrom
fix-composer-github-auth

Conversation

@rfay

@rfay rfay commented Jul 25, 2026

Copy link
Copy Markdown
Member

Summary

A drupal12 workspace on staging failed all three composer install attempts with Could not authenticate against github.com, and never reached the Drupal install. The cause is the Composer GitHub OAuth token setup added in #166.

coder external-auth access-token github prints the token to stdout and exits 0 — but when the user has no linked GitHub account it prints the authentication URL to stdout and exits 1. The capture discarded that exit code with || true, so the workspace configured this as its OAuth token:

[github-oauth.github.com] https://staging-coder.ddev.com/external-auth/github

Composer then failed every dist download and, because it disables source fallback once authentication fails, never retried anonymously:

Failed to download mglaman/composer-drupal-lenient from dist: Could not authenticate against github.com
    Source fallback is disabled. Not trying alternative sources.
In AuthHelper.php line 152:
  Could not authenticate against github.com

The retry loop can't help — nothing about this is transient. A bad token is strictly worse than no token: with none, Composer downloads anonymously and succeeds. Verified in the failing workspace — unsetting the value alone makes ddev composer install exit 0.

  • Honor the exit code, so only a real token is used.
  • Validate the token against api.github.com/user before configuring it, so an expired or revoked GITHUB_TOKEN fails loudly here rather than opaquely mid-install. This matters for the CI path in particular: drupal-integration-test.yml passes GitHub Actions' ephemeral secrets.GITHUB_TOKEN as a template variable, so any workspace created from that template version after the workflow ends gets a long-expired token.
  • Unset any token a previous run configured when we don't have a valid one. Composer's global config lives in a persistent DDEV volume, so a bad value keeps breaking every later start even once this code stops writing one — without this, already-affected workspaces stay broken.
  • Skip the cache rebuild when SETUP_FAILED is set. It sat outside the guard and appended a misleading drush is not available / Failed to run drush cr after the real failure, burying the cause.

Applied to drupal-contrib too, which had a byte-identical copy of the token block. (Its separate Drupal-12 Guzzle problem is untouched — see #179.)

Why this surfaced now

Not a Coder change. Both deployments run identical Coder (v2.34.4+19d9274) with different deployment_ids, so external-auth links are per-deployment. The only recent commit touching coder external-auth access-token (coder/coder#26883) is not in any release, and preserves the exit-code behavior anyway.

The bug has been latent since #166. Three paths supply a token, and only the third is broken:

Path github_token var external-auth link Result
Human workspace on production empty linked real token — worked for weeks
CI integration-test workspace set via --variable github_token= not consulted real token — CI green
Human workspace on staging empty (staging-push.yml doesn't pass it) absent URL used as token — fails

Test plan

  • terraform fmt -recursive clean
  • terraform validate passes for drupal-core and drupal-contrib
  • terraform test passes for both (6 + 6)
  • bash -n on both rendered startup_script heredocs (Terraform escapes resolved) — terraform validate does not check the embedded shell
  • Reproduced in a live staging workspace: [github-oauth.github.com] held the external-auth URL; clearing it made ddev composer install exit 0
  • Exercised all three new paths in that workspace: access-token exit 1 → URL not used; garbage token → rejected by the api.github.com/user check; --unset → safe no-op when nothing is configured
  • Ran the remaining setup steps verbatim afterward: Drush installed via the Guzzle fallback, drush si -y demo_umami[OK] Installation complete, drush cr OK, bootstrap successful, http 200
  • Fresh workspace built from the pushed template — not yet run

Release/Deployment Notes

The fix makes an unlinked account a clean anonymous-download path rather than a hard failure. If authenticated downloads are wanted for ordinary users, that's a separate change: add data "coder_external_auth" "github" { id = "github"; optional = true } so Coder surfaces a "Login with GitHub" button at workspace creation. No template currently declares one, which is why nothing ever prompted for the link.

Workspaces already poisoned by the old code self-heal on next start via the --unset branch.

🤖 Generated with Claude Code

`coder external-auth access-token github` prints the token to stdout and
exits 0, but when the user has no linked GitHub account it prints the
*authentication URL* to stdout and exits 1. The token capture discarded
that exit code with `|| true`, so workspaces whose owner had never
authorized the deployment's GitHub app configured this as their OAuth
token:

  [github-oauth.github.com] https://staging-coder.ddev.com/external-auth/github

Every github.com dist download then failed with "Could not authenticate
against github.com", and because Composer disables source fallback once
authentication fails, all three composer install attempts failed the same
way — the retry loop can't help, nothing here is transient. This is
strictly worse than configuring no token at all: with none, Composer
downloads anonymously and succeeds, which is exactly what happens once
the bogus value is unset.

- Honor the exit code, so only a real token is used.
- Validate the token against api.github.com/user before configuring it,
  so an expired or revoked GITHUB_TOKEN fails loudly here instead of
  opaquely during install.
- Unset any token a previous run configured when we don't have a valid
  one. Composer's global config lives in a persistent DDEV volume, so a
  bad value keeps breaking every later start even after this code stops
  writing one.

Also stop running the cache rebuild when SETUP_FAILED is set — it sat
outside the guard and appended a misleading "drush is not available"
error after the real failure.

Applied to drupal-contrib too, which had an identical copy of the block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-07-25 16:11 UTC

@rfay
rfay merged commit 04367e8 into main Jul 25, 2026
14 checks passed
@rfay
rfay deleted the fix-composer-github-auth branch July 25, 2026 16:11
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.

1 participant