Commit

46d4f6c

Anyone else struggling with Claude Code cloud environments and Composer install (especially when phpstan is a dependency)? Claude Code’s cloud sandbox blocks GitHub archive downloads, so Composer falls back to cloning every package’s full history, which is slow and takes up…
my (well, Claude's) current fix is a composer wrapper in the env setup script shallow-fetches each locked commit, git-archives it into Composer’s cache, then installs in 40s, downloading 'only' 427MB and phpstan works. But this may well be a skill issue on my part!
## Getting composer install to work in Claude Code cloud sessions **The problem.** In Claude Code's cloud sandbox, third-party GitHub archive downloads (the GitHub API zipball endpoint and codeload.github.com) come back 403. Composer quietly falls back to cloning each package's full git history, so vendor/ grows from a few hundred MB to several GB and installs crawl. phpstan/phpstan doesn't even get that far. It deliberately publishes no git source, because its repo holds every release's phar and comes to about 7 GB. With no archive and nothing to clone, the install just fails. **The fix.** Plain git fetch from github.com still works. So, before Composer runs, the setup script builds each archive itself and drops it into Composer's download cache, where Composer will find it: 1. Read composer.lock. For every package whose dist is a GitHub zipball, work out the file name Composer expects in its cache: a folder named after the package, holding a zip named after the SHA-1 hash of the package's download URL. 2. Do a shallow git fetch --depth 1 of the exact locked commit, then git archive --format=zip. git archive honours export-ignore, so you get the same files GitHub's zipball would have. 3. Write each zip into the cache. Composer then installs from cache and never touches the blocked host. It runs 16 fetches in parallel and skips packages that are already cached. Any package it can't seed is left for Composer to fetch as usual, so it can't make things worse than before. **Making it work everywhere, not per repo.** Repo SessionStart hooks don't run in cloud sessions that use several repos. So this lives in the **cloud environment's setup script**, which runs once and is cached for about a week. The script puts a small wrapper in front of composer:
bash
# /usr/local/bin/composer (the real binary moved to composer.real)
case " $* " in
  *" install "*|*" update "*|*" require "*) composer-seed || true ;;
esac
exec /usr/local/bin/composer.real "$@"
composer-seed does the cache seeding above for whatever lockfile is in the current directory. It also sets up the login for private package registries (Flux Pro, Filament) using values stored in the environment settings. The setup script never fails because of it (|| true), so a bad run never blocks a session from starting. **Results:** - **A lock with about 270 packages.** Before, it made multi-GB clones and phpstan aborted the install. After, it takes 40 s and vendor/ is 427 MB. - **A project with private registries.** Before, the same problems. After, it takes 12 s and vendor/ is 360 MB. **Known gap.** composer update and composer require only pre-seed what the *old* lock pinned. Newly resolved versions still come in as git clones. The fix would be composer update --no-install, then seed, then install.

commit 46d4f6c