# Investigate Slow Page Loads in a Laravel App

Yes. An agent reads your Laravel controllers, permission queries and recent commits, then ranks the likely causes of the slowdown with file, line and a check.

[Run this on Rush](https://cloud.getrush.ai/run/investigate-slow-page-loads/laravel/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and starts from the trace you describe: book and chapter pages at 3 s p95 instead of 400 ms, the time spent in database queries, after a permissions change in the last release. Time in queries after a permissions change narrows the search to two places: the SQL that decides what the current user may see, and how many times a page runs it.

In an app like BookStackApp/BookStack it follows a book page from the route to the database. `BookController::show` loads the book, then builds the contents with `(new BookContents($book))->getTree(true)` at line 132. `app/Entities/Tools/BookContents.php` loads the book's visible pages through `PageQueries::visibleForList`, which adds a correlated `book_slug` subquery per row, and its visible chapters with `scopes('visible')`. Every one of those goes through `scopeVisible` in `app/Entities/Models/Entity.php`, which calls `PermissionApplicator::restrictEntityQuery`. A chapter page does the same work and more: `ChapterController::show` loads the chapter's pages at line 87 and then the whole book tree again at line 89 for the navigation.

`restrictEntityQuery` at line 99 of `app/Permissions/PermissionApplicator.php` is the heart of it. For every candidate row it adds a `whereHas('jointPermissions')` subquery that filters `joint_permissions` by the user's role ids, groups by `entity_type` and `entity_id`, and keeps the row only if `max(status)` and `max(owner_id)` pass a `HAVING` clause. The agent reads the indexes that subquery can use: the 2023 storage refactor indexed `status` and `owner_id` separately, alongside the older `role_id` and `(entity_id, entity_type)` indexes. It notes that the query's cost grows with the number of roles a user has and the number of `joint_permissions` rows per entity, both of which a permissions change can multiply, and that `bookstack:regenerate-permissions` rebuilds the table.

Last, it reads the 30 most recent commits with `git log -p`, looking for changes to `PermissionApplicator`, `JointPermissionBuilder`, `EntityPermissionEvaluator`, the entity queries and the migrations, and for any new call that runs a permission check per item in a loop. If it needs to see the SQL, it runs the PHPUnit tests that cover these pages in the sandbox with MySQL and the query log on. It writes `incident.md` and changes no code.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Incident: Datadog APM shows the book and chapter pages at 3 s p95, up from 400 ms, with the time in database queries, after a permissions change in the last release. Read the code, the configuration and the last 30 commits, find the most likely causes, and write incident.md with a timeline, each suspect with file and line evidence, and the check that would confirm or clear it. Change no code.",
  "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

What comes back is `incident.md`, attached to the session as an artifact. It opens with the commits in the last release that touched permissions, queries or migrations, then ranks the suspects. Each names the file and line, describes the query it produces and why that query got slower, and gives the check: the slowest query spans on the book page's trace in Datadog APM, `EXPLAIN` on the permission subquery with a real user's role ids, the row count of `joint_permissions` per entity before and after the release, or the number of permission queries a single chapter page issues.

Where the agent ran tests or captured SQL in the sandbox, the report quotes the statements and the counts and marks them as sandbox results on test data. The session's transcript keeps every command and every file read.

A reviewer checks the top suspect against the production trace first. If the cause is a stale or oversized `joint_permissions` table, the fix may be operational (regenerating permissions) rather than code; if it is a query or a missing index, a follow-up to the same session can ask the agent to write the change as a pull request with a test.

## Run it on every Datadog latency monitor

A Datadog monitor on p95 latency for a resource can notify a webhook. Point it at a handler on your server that sends this request with the resource names, the before and after numbers and the release in the prompt, so each regression starts its own investigation.

With a webhook URL on the API key, Rush POSTs `run.completed` when the turn finishes; your handler adds the report's link to the monitor's event or the incident.

## Why run it on Rush?

Permission-aware queries are hard to read in a trace and harder to read in code, because one scope adds a grouped subquery to every list on the page. The agent traces that path from controller to SQL across the whole repository in its own sandbox, with PHP, Composer and MySQL available to run the tests and log the queries, without loading anything into a shared database.

Every suspect comes with its file and line and every measurement with its command, kept in the session's transcript. If several pages regressed at once, each can be investigated in its own run, up to the key's concurrent run limit.

## Frequently asked questions

### Can the agent read our Datadog traces directly?

Yes, if you connect Datadog's MCP server and name the connection in the request. It can then search the slow traces and spans itself instead of relying on the numbers in the prompt. Without it, the report tells you which spans to open.

### Why would a permissions change slow down pages that did not change?

In apps like BookStack, every list of books, chapters and pages filters rows through a permissions table. A change that adds roles, per-item overrides or rows to that table makes every one of those filters more expensive, even on pages nobody edited.

### Will it add indexes or run migrations?

No. The prompt says to change no code, and the agent works on its own copy in the sandbox. The report may recommend an index, with the `EXPLAIN` evidence for it, for your team to decide on.

### Does it work with Telescope, Debugbar or Clockwork output?

Yes. BookStack ships Clockwork as a dev dependency, and the agent can use it or Laravel's query log in the sandbox. You can also attach an export from Telescope or Debugbar captured in production to the request.
