What the agent does
Your backend starts one session for each customer's repository and the agent works on its own clone. In a Laravel codebase like koel/koel the first files it opens are composer.json, composer.lock, package.json and, because the repository has one, AGENTS.md, whose Laravel Boost guidelines tell an agent to read the exact package versions before doing anything. It finds php >=8.3, laravel/framework ^13.0 locked at v13.37.0, required extensions such as ext-exif, ext-gd and ext-fileinfo, roave/security-advisories at dev-latest in require-dev, and a Vue front end managed by pnpm 10.32.1. It installs the PHP extensions the lock asks for, runs composer install, and then composer outdated --direct --major-only to list only the direct packages a major version or more behind. A constraint like predis/predis ~1.0, locked at v1.1.10, is the kind of row it checks against Packagist and reports with both versions.
Then it finds every test command the project defines, not just one. composer.json has "test": "@php artisan test --parallel", which runs PHPUnit through ParaTest; phpunit.xml.dist defines feature, unit and integration suites and sets DB_CONNECTION=sqlite with DB_DATABASE=:memory:, plus a long list of service keys blanked or faked for tests. The GitHub workflows run the same suite three ways (test-backend-sqlite.yml, test-backend-pgsql.yml, test-backend-mariadb.yml, all on PHP 8.3), and the front end has pnpm test (vp test) and pnpm typecheck (vue-tsc --noEmit). The agent runs php artisan key:generate the way the workflows do, then composer test, pnpm install --frozen-lockfile, pnpm test and pnpm typecheck, and records each command with whether it passed.
For risky patterns it reads the places Laravel apps usually go wrong and judges each one. Raw SQL is the first: whereRaw, selectRaw and DB::raw appear in app/Repositories/ (SongRepository.php, AlbumRepository.php, PlayRepository.php among them), and the agent checks each call for request input interpolated into the string instead of bound as a parameter. In a streaming app it follows the paths from PlayController, DownloadSongController and app/Services/Streamer/ to the filesystem, looking for a path built from the request. It checks unescaped Blade output ({!! !!}), mass-assignment guards on models, and routes registered without auth middleware. A hit that turns out to be safe, such as the {!! $slot !!} in the vendored mail template, is left out; each one kept has its file, line and reason.
The prompt forbids changing any file. The agent writes nothing back to the repository, and the setup it needs, from the generated app key to the SQLite database, stays in its own sandbox. Its final answer is the JSON with the three arrays.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "codex",
"input": "Audit this Laravel repository without changing any tracked file. Return only JSON with three arrays. outdated_dependencies: direct Composer and npm packages whose latest release is at least one major version ahead of what the lockfile installs, with name, ecosystem (composer or npm), locked and latest. tests: every test and type-check command the project defines in composer.json, package.json and its CI workflows, with the command, passed true or false, and the last 20 lines of output when it failed. risky_patterns: up to ten patterns such as raw SQL built from request input, unescaped Blade output of user data, file paths built from the request, or models with no mass-assignment guard, each with file, line, pattern and a one-sentence reason. Install whatever PHP extensions and services the tests need inside the sandbox.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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.
- 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.
- 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 answer is the JSON, returned as the session's result and in the run.completed webhook's data.result when it is 16 KiB or less; past that, your server reads data.session_url with the key to get it. Because a Laravel app has two package managers, every outdated_dependencies entry names its ecosystem, so your product can group Composer and npm findings. The tests array has one entry per command, so a customer sees that composer test passed while pnpm typecheck failed, with the tail of the failing output beside it.
Your product checks the JSON before using it: three arrays, the expected fields, at most ten risky patterns. Anyone spot-checking an audit should confirm that each raw-SQL finding really involves request input rather than a constant, and that a test failure is not a missing PHP extension the sandbox could have installed. The transcript holds the composer outdated output, each test run and each file the agent opened, so a disputed line can be traced to its source.
Run it on every pull request merged to the customer's main branch
A dev tool can re-run the audit whenever a customer's default branch moves. Your GitHub webhook handler sees the push, starts a session with the repository in resources and an Idempotency-Key made from the repository and the commit SHA, so a redelivered GitHub event returns the same run instead of starting a second. Keep the returned execution_id with the customer and commit in your database.
When the turn ends, Rush signs and POSTs run.completed to the key's webhook URL. Your handler checks webhook-signature, drops any webhook-id it has already processed (Rush retries with the same id), finds the commit by execution_id and stores the JSON next to it, so the customer sees the audit change commit by commit.
The use case this job follows, with its automation and trigger: Run an agent per customer task.
Why run it on Rush?
A Laravel test run needs PHP with the right extensions, Composer, Node, pnpm and often a database, and it executes the customer's code. Doing that for many customers on your own infrastructure means one shared machine running many strangers' code. Each Rush session is its own sandbox, so a customer's composer install, generated app key and test database exist only there.
Many customers can be audited at once, each in its own sandbox, up to the key's concurrent run limit, and each result is backed by a transcript of real commands. When a customer asks why a package is flagged or a test is marked failed, your support team opens the session and shows the command and its output instead of re-running anything.
Frequently asked questions
Does the audit cover the JavaScript side of a Laravel app?
Yes, when the repository has one. The prompt asks for npm packages as well as Composer ones and for every test and type-check command in package.json, so a Laravel app with a Vue or React front end gets both in one audit, each entry marked with its ecosystem.
What if the PHPUnit suite needs MySQL or Postgres?
The agent reads phpunit.xml.dist and the CI workflows to see which database the suite expects. When it is SQLite in memory, as in many Laravel apps, nothing else is needed; when it is MySQL or Postgres, the agent starts that database in the sandbox and points the test environment at it.
Why codex in the request instead of claude?
Either works; agent is the harness that does the job and both read and run a Laravel repository. Pick the one your org has an account for, or run the same prompt with each on a few repositories and keep the one whose audits your team prefers.
Can it run on a private customer repository?
Yes, if your org's Rush GitHub App installation covers it. GET /o/{org}/github lists the repositories each installation reaches; a repository outside them is refused with 422 RESOURCE_NOT_ACCESSIBLE, and no sandbox starts.