Skip to content

fix(drupal-contrib): install Drush via Guzzle alias on Drupal 12 HEAD#181

Merged
rfay merged 1 commit into
mainfrom
fix-drupal-contrib-drush-guzzle8
Jul 25, 2026
Merged

fix(drupal-contrib): install Drush via Guzzle alias on Drupal 12 HEAD#181
rfay merged 1 commit into
mainfrom
fix-drupal-contrib-drush-guzzle8

Conversation

@rfay

@rfay rfay commented Jul 25, 2026

Copy link
Copy Markdown
Member

Summary

The skipto D12 cell of the contrib integration tests has failed since 2026-07-25 (run 30165116093). All three ddev poser attempts die at dependency resolution:

- drush/drush 14.x-dev requires guzzlehttp/guzzle ^7.0 -> satisfiable by guzzlehttp/guzzle[7.5.x-dev, ..., 7.15.x-dev]
- You can only install one version of a package ...: guzzlehttp/guzzle[..., 8.0.0, 8.0.x-dev]

No web/index.php gets created, which is the assertion that fails. Same root cause as #178: core main moved to guzzlehttp/guzzle: "^8.0" on 2026-07-24 (drupal.org #3612544) while every Drush version still requires ^7.0. The break is module-agnostic — any module on D12 hits it, the D11 cells pass, and contrib was green through the 07-24 nightly and red from 07-25. Not a stale issue fork: the token issue fork cell passed in the same run.

#178 fixed drupal-core and deliberately scoped this template out, because its technique does not transfer. There the exact Guzzle version is read from composer.lock after composer install has already succeeded; here ddev poser resolves core and Drush in a single solve, so the solve fails before any lock exists (and poser deletes composer.contrib.lock afterwards). #178's Notes and #179's Related section both ask for a separately tested approach. This is it.

  • Retry with an inline Guzzle alias when a poser attempt fails and its output shows the Drush/Guzzle conflict: add "guzzlehttp/guzzle": "<8.x> as 7.99.0" to the module's require-dev and re-run immediately. expand_composer_json.php merges root require-dev into composer.contrib.json, so the alias reaches the install. The version comes from packagist, since Composer rejects a range as an alias source (the alias source must be an exact version) and there is no lock to read.
  • Gated on that specific failure, so D11 workspaces (core requires Guzzle ^7) keep resolving untouched, and the workaround self-clears the day Drush is fixed. Removal is tracked in Revert Drush/Guzzle 8 workaround once Drush supports Guzzle 8 #179.
  • Capture each attempt's output to its own file. $SETUP_LOG is append-only across workspace restarts, so grepping it could match a stale conflict from an earlier start and wrongly pin Guzzle 8 on a D11 build.
  • Mirror the fallback in drupal-contrib/scripts/test-issue-branches.sh, and capture poser's exit code there — the previous ddev poser 2>&1 | tail -10 form tested tail's status, so a poser failure went undetected without pipefail.
  • Make the PR smoke cell skipto D12 instead of token D11. The PR matrix ran only D11, which is why this class of breakage reached main green while every PR stayed green. Same one-workspace cost; D12 tracks core HEAD so it breaks first, and the D11 cells still run on push-to-main and nightly.

Test plan

  • terraform fmt -recursive clean
  • terraform validate passes for drupal-contrib
  • terraform test passes for drupal-contrib (6 passed)
  • bash -n drupal-contrib/scripts/test-issue-branches.sh clean
  • Workflow YAML parses; actionlint reports no new findings (only pre-existing nits in the if: false self-hosted jobs)
  • Reproduced the CI failure against real DDEV on skipto with DRUPAL_CORE=^12, following the same steps as the template: plain ddev poser exits 1 with the exact conflict above
  • Verified the fallback in that project: packagist yields 8.0.0 (the only stable 8.x), the retry exits 0 and installs drupal/core dev-main + drush/drush 14.x-dev + guzzlehttp/guzzle 8.0.0 together
  • Verified everything the failing job asserts: web/index.php present, drush si minimal[OK] Installation complete, drush statusDatabase: Connected and Drupal 12.0-dev, drush en skipto → installed and listed by pm:list --status=enabled
  • Confirmed skipto itself is D12-compatible today (it enables cleanly on core dev-main), since it is now the PR gate
  • Fresh drupal-contrib D12 workspace from the pushed template — this PR's own CI run now covers that, which is the point of the matrix change

Notes

  • The alias lands in the module's tracked composer.json, not in gitignored composer.local.json as in drupal-core. The existing git update-index --skip-worktree composer.json call covers it, same as the drush require-dev line, and the contrib workflow has no dirty-tree assertion. Revert Drush/Guzzle 8 workaround once Drush supports Guzzle 8 #179 has been updated with the contrib-specific removal steps.
  • Pinning an exact Guzzle 8 version is safe only while drupal/core-recommended does not pin Guzzle itself — it currently does not (core requires the ^8.0 range and the metapackage carries no Guzzle entry). If core-recommended later pins a different exact version, the packagist lookup would need to read that pin instead.
  • Making D12 the PR gate means unrelated core or contrib churn can turn PRs red. That exposure already exists nightly on main; this moves the signal earlier.
  • Drupal 11.x and 10.x workspaces are unaffected.

🤖 Generated with Claude Code

Drupal core `main` moved to `guzzlehttp/guzzle: "^8.0"` on 2026-07-24
(drupal.org #3612544) while every Drush version still requires `^7.0`.
The `skipto` D12 cell has failed since, with all three `ddev poser`
attempts dying at dependency resolution:

    - drush/drush 14.x-dev requires guzzlehttp/guzzle ^7.0 -> satisfiable
      by guzzlehttp/guzzle[7.5.x-dev, ..., 7.15.x-dev]
    - You can only install one version of a package ...: guzzlehttp/guzzle[
      ..., 8.0.0, 8.0.x-dev]

No `web/index.php` gets created, which is the assertion that fails. The
break is module-agnostic — any module on D12 hits it, and the D11 cells
pass. Contrib was green through the 07-24 nightly and red from 07-25.

#178 fixed the same conflict in drupal-core and deliberately scoped this
template out, because its technique does not transfer: there the exact
Guzzle version is read from `composer.lock` after `composer install` has
already succeeded, but `ddev poser` resolves core and Drush in a single
solve, so the solve fails before any lock exists (and poser deletes
`composer.contrib.lock` afterwards). #178's Notes and #179's Related
section both call for a separately tested approach here. This is it.

- When a poser attempt fails *and* its output shows the Drush/Guzzle
  conflict, add `"guzzlehttp/guzzle": "<8.x> as 7.99.0"` to the module's
  `require-dev` and retry immediately. `expand_composer_json.php` merges
  root `require-dev` into `composer.contrib.json`, so the alias reaches
  the install. The exact version comes from packagist, since Composer
  rejects a range as an alias source and there is no lock to read.
- Gated on that specific failure, so D11 (core requires Guzzle `^7`)
  keeps resolving untouched, and the workaround self-clears the day Drush
  is fixed. Removal is tracked in #179.
- Capture each attempt's output to its own file. `$SETUP_LOG` is
  append-only across workspace restarts, so grepping it could match a
  stale conflict from an earlier start and wrongly pin Guzzle 8 on a D11
  build.
- Mirror the fallback in `drupal-contrib/scripts/test-issue-branches.sh`,
  and capture poser's exit code there — the previous `cmd | tail -10`
  form tested `tail`'s status, so a poser failure went undetected.
- Make the PR smoke cell `skipto` D12 instead of `token` D11. The PR
  matrix ran only D11, which is why this reached main green. Same
  one-workspace cost; D12 tracks core HEAD, so it breaks first, and the
  D11 cells still run on push-to-main and nightly.

Unlike drupal-core, where the alias lands in gitignored
`composer.local.json`, here it lands in the module's tracked
`composer.json`. The existing `git update-index --skip-worktree
composer.json` call covers it, same as the drush `require-dev` line.

Verified against real DDEV on `skipto` with `DRUPAL_CORE=^12`: plain
poser exits 1 with the CI conflict; the fallback installs
`drupal/core dev-main` + `drush/drush 14.x-dev` + `guzzlehttp/guzzle
8.0.0` together; `web/index.php` present; `drush si minimal` reports
`[OK] Installation complete`; `Database: Connected`; `drush en skipto`
installs and `pm:list --status=enabled` lists it; `drush status` reports
Drupal 12.0-dev. Also `terraform fmt -recursive` clean, `terraform
validate` and `terraform test` pass for drupal-contrib (6 passed),
`bash -n` clean on the script, workflow YAML parses with no new
actionlint findings.

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 18:37 UTC

@rfay
rfay merged commit f73a96c into main Jul 25, 2026
16 checks passed
@rfay
rfay deleted the fix-drupal-contrib-drush-guzzle8 branch July 25, 2026 18:37
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