All jobs

Upgrade Symfony Dependencies Every Week

Yes. Each week an agent bumps your Composer packages inside the Symfony version Flex pins, runs phpunit and phpstan, reverts what breaks and opens one pull request.

Run this on RushIn a Project that has your repository.

What the agent does

In a Symfony app like symfony/demo, composer.json requires php >=8.4 with config.platform.php set to 8.4.1, every symfony/* package at ^8, doctrine/orm ^3.5 with doctrine/doctrine-bundle ^3.0 and doctrine/dbal ^4.0, league/commonmark, symfony/asset-mapper, symfony/ux-live-component and the three twig extra packages. The dev side has phpunit ^11.5.50, dama/doctrine-test-bundle, phpstan ^2.0 with the doctrine and symfony extensions, friendsofphp/php-cs-fixer, symfony/browser-kit and symfony/maker-bundle. Two settings shape every upgrade: extra.symfony.require is 8.1., so Flex keeps every symfony/ package on the 8.1 line whatever the caret allows, and the platform setting makes Composer resolve for PHP 8.4.1 whatever PHP the sandbox runs. The agent keeps both, so the lockfile stays installable on the oldest PHP the project supports and the framework does not jump a minor by accident.

It runs composer outdated --direct to see what is behind, then composer update --with-dependencies one family at a time: the symfony/* packages together, since Flex pins them as a set; doctrine/orm with doctrine-bundle and dbal; twig/twig with the extra bundles; phpunit with phpstan and its extensions. composer.lock records where it starts, with symfony/framework-bundle at v8.1.0, doctrine/orm at 3.6.7, doctrine/doctrine-bundle at 3.2.4, twig/twig at v3.27.1, phpunit at 11.5.55 and phpstan at 2.2.2. The auto-scripts in composer.json run cache:clear, assets:install, importmap:install and sass:build after every update, so a Flex-installed config or asset change that breaks the container fails at update time, before any test runs. symfony.lock changes when Flex installs or updates a package's configuration files, and the agent keeps that diff in the pull request rather than discarding it.

The tests are functional and need no server. .env.test points DATABASE_URL at an SQLite file under data, tests/bootstrap.php loads the dotenv files, and phpunit.dist.xml registers the DAMA extension so each test runs inside a transaction that is rolled back. The same file sets failOnDeprecation, failOnNotice and failOnWarning to true, which is what makes a weekly upgrade safe to automate: a package that starts triggering a deprecation fails the suite instead of passing with a warning nobody reads. The agent runs vendor/bin/phpunit after each family, exactly as .github/workflows/tests.yaml does on PHP 8.4 and 8.5, then vendor/bin/phpstan analyze as lint.yaml does, and the console lint commands that workflow runs: lint:yaml on config, lint:twig on templates, lint:xliff and lint:translations, lint:container, and doctrine:schema:validate --skip-sync. It also runs composer audit, which the lint workflow runs too.

An update that fails any of those is reverted in composer.json, composer.lock and symfony.lock. The agent keeps the failing test with its message, the deprecation text, or the phpstan error with file and line, for the pull request. A Flex update that rewrote a file under config is noted, since a reviewer wants to see a changed security.yaml or doctrine.yaml rather than discover it in the diff.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Upgrade this project's Composer dependencies to their latest compatible releases, keeping config.platform.php and extra.symfony.require in composer.json as they are. Run composer outdated --direct first, then update one package family at a time with composer update --with-dependencies: the symfony packages together, doctrine together, twig together, then the dev tools. After each family run vendor/bin/phpunit, vendor/bin/phpstan analyze, composer audit and the console lint commands .github/workflows/lint.yaml runs (lint:yaml config, lint:twig templates, lint:xliff translations, lint:translations, lint:container, doctrine:schema:validate --skip-sync). Revert any family that fails any of them and record the failing test, deprecation or phpstan message. Open one pull request listing each bump with its old and new locked version, each config file Flex rewrote, and each skipped upgrade with the error that blocked it.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
  1. Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
  2. Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
  3. Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

What comes back

The pull request changes composer.lock for every update, composer.json only where a constraint had to rise, and symfony.lock plus any file Flex rewrote. The description lists each package with its old and new locked version, grouped by family, then each file Flex rewrote, then each update held back with the failing test, deprecation or phpstan message. An update that required a code change, such as a renamed configuration key or a changed method signature, is listed separately with the files it touched.

The pull request's own checks run the same suite on PHP 8.4 and 8.5 on Linux, and 8.4 on macOS and Windows, plus the lint and phpstan workflow, so the sandbox run and CI agree before anyone reads the diff. A reviewer reads the Symfony and Doctrine changelogs for the versions that moved, opens the Flex diff if symfony.lock changed, and merges.

When nothing is behind, the result says so and no pull request is opened. When a framework minor is available beyond what extra.symfony.require allows, the description says which one and what raising the pin would pull in, and leaves that decision to the reviewer.

Run it on every Monday morning

Add an automation to the Project with this prompt and a schedule trigger, cron 0 9 * * 1 in your timezone, so each week's update is small enough to read. composer.lock only changes when someone runs composer update, and a blog-sized Symfony app sits behind for months otherwise.

Give the API key a webhook URL and Rush POSTs run.completed when the turn ends, with data.session_url to read the pull request from. Set the automation's max runs per hour to one so a retried fire does not open a second pull request.

The use case this job follows, with its automation and trigger: Upgrade dependencies weekly.

Why run it on Rush?

A Symfony test run wants PHP with intl, mbstring, pdo_sqlite and zip, Composer 2, and the console lint commands that only pass once the container compiles. In a Rush sandbox the agent builds that environment from the project's own workflow files and keeps every composer, phpunit, phpstan and console command with its output, so the pull request carries evidence rather than a summary.

A bot that bumps symfony/form alone can leave symfony/validator a version behind and the container failing to compile. One run that moves families together, respects the Flex pin and tests each family gives one reviewable pull request a week, and several Symfony apps can run the same request at the same time.

Frequently asked questions

Will it move Symfony to a new minor?

No. extra.symfony.require pins the 8.1 line and the agent leaves it. The pull request names the newer minor when one exists and what raising the pin would change, and a reviewer decides.

What if Flex rewrites a config file?

Flex installs each package's configuration files during composer update and records them in symfony.lock. The agent keeps that diff, lists the file in the description, and the lint:container and schema:validate checks confirm the rewritten config still compiles.

Does it need a database server?

No. .env.test points the test environment at an SQLite file and the DAMA extension rolls each test back. Nothing connects to your servers.

A dependency started triggering a deprecation. Is that a failure?

Yes, because phpunit.dist.xml sets failOnDeprecation. The family is reverted and the deprecation text goes in the pull request so you can fix the call site before the next weekly run picks the update up again.