# Reproduce a Bug from a Linear Issue in a Phoenix App

Yes. A Linear bug report becomes a failing LiveView test, then a fix, in a pull request that links the issue and shows both results.

[Run this on Rush](https://cloud.getrush.ai/run/reproduce-linear-bug/phoenix/), in a Project that has your repository.

## What the agent does

The agent reads the Linear issue before it reads the code, and it writes the failing test before the fix. For a report that says deleting a song leaves it in other users' playlists until reload, a Phoenix app shaped like fly-apps/live_beats has a precise place to look. mix.exs sets app :live_beats, version 0.1.1, elixir ~> 1.12, phoenix ~> 1.7.1, ecto_sql ~> 3.11 and postgrex. The test alias is ecto.create --quiet, ecto.migrate --quiet, then test. Songs are LiveBeats.MediaLibrary.Song, schema songs, belonging to a user. MediaLibrary.delete_song/1 removes the mp3 with delete_song_file, deletes the row inside an Ecto.Multi, decrements later positions, updates the user's songs_count, and on success calls broadcast! with Events.SongDeleted.

The LiveView path is the part the bug names. ProfileLive.handle_event("delete") loads the song and calls delete_song only when song.user_id is the current user. It does not itself remove the row from the stream. ProfileLive.handle_info for Events.SongDeleted does: it decrements songs_count and calls stream_delete(:songs, song). mount subscribes with MediaLibrary.subscribe_to_profile, which is a PubSub subscribe on the profile user's topic. PlayerLive's handle_info for SongDeleted only stops playback when the deleted id is the song that view is playing. It does not render a playlist. The existing test in test/live_beats_web/live/profile_live_test.exs clicks #delete-modal-#{song.id}-confirm and then mounts a fresh live/3 before it refutes the title. That checks the database. It does not check a second socket that was already open.

The reproduction goes in the empty block. That file's setup logs in chrismccord and creates mrkurt, and describe "viewing other profiles" is a TODO. The agent fills that block with a LiveView test: the owner uploads test/support/fixtures/silence1s.mp3 the way the existing upload test does, a second conn is logged in as the other user and mounted on the owner's profile path, the owner clicks the delete confirm on the already-mounted view, and the second view's render must drop the title without another live/3. The agent runs that test and keeps the failure. Only then does it change delete_song, the broadcast, or the handle_info clause so the same test passes. The commented lock_playlist(user.id) inside delete_song is left commented unless the failure shows a race that the lock would close.

The suite it runs is mix test, which is the alias, so the test database is created and migrated first. It does not start the Phoenix server or the esbuild watcher. Floki is already a test-only dependency, and Phoenix.LiveViewTest is what profile_live_test.exs imports. A failure in accounts_test.exs or the GitHub callback tests is reported rather than edited, because those files do not render the song stream.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "The Linear issue reports: deleting a song leaves it in other users' playlists until reload. Reproduce it with a failing test first, then fix the cause. In an app laid out like fly-apps/live_beats, read MediaLibrary.delete_song/1 and ProfileLive's delete event and SongDeleted handle_info. The existing profile_live_test.exs only refutes the title after a fresh live/3, and \"viewing other profiles\" is empty. Add a test there with two connections: the owner deletes on an already-mounted view, and the other user's mounted view must drop the song title without remounting. Use test/support/fixtures/silence1s.mp3. Run mix test, show the failure before the fix and the pass after, and open a pull request that links the issue. Do not uncomment lock_playlist unless the failure requires it.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    },
    {
      "url": "https://linear.app/your-workspace/issue/ENG-42"
    }
  ]
}
```

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 a pull request that links the Linear issue. The diff adds the two-connection test under describe "viewing other profiles" in profile_live_test.exs, and the production change is in media_library.ex or profile_live.ex, whichever the failing test implicated. The body shows the mix test failure from before the fix and the passing run after, including the line that refutes the song title on the second view. The owner's existing upload-and-delete test is still there.

The session names the repository and the pull request. The transcript keeps both mix test runs, so a reviewer can see the assertion that failed when the second LiveView was not updated. The Linear issue URL in resources is what put the report in front of the agent. The pull request is the thing you merge.

Before merging, a reviewer checks that the new test does not call live/3 a second time on the viewer's conn, because that would pass even if the live update was still broken. They check that delete is still refused when the current user does not own the song, and that songs_count moves with the stream.

## Run it on every Linear issue labelled bug

When a Linear issue receives the label you use for this job, have your server POST https://api.prix.dev/o/{org}/sessions with the repository and the issue URL in resources. The issue URL is https://linear.app/<workspace>/issue/<ID>, and it is read through the caller's Linear connection. A repository the GitHub App does not cover, or an issue with no Linear connection, is 422 RESOURCE_NOT_ACCESSIBLE. An Idempotency-Key of the issue id plus the label keeps a repeated webhook from opening two pull requests.

The key's webhook receives run.completed or run.failed, with session_url for the pull request. You can comment that URL back onto the Linear issue from your server. max_concurrent, 4 by default, caps how many labelled bugs run at once. A 429 means the next issue waits.

## Why run it on Rush?

Reproducing a LiveView bug means two sockets, a migrated test database, and a test that fails for the right reason before anyone edits delete_song. That is awkward on a laptop that is already running mix phx.server. The agent does it in a sandbox: mix test creates and migrates the test database, the transcript keeps the red run and the green run, and your local Postgres is not involved.

The pull request is proof that the report was reproduced, not a guess at the broadcast. The second view's assertion is in the diff, and both test outputs are in the transcript. Other labelled bugs run in their own sandboxes, so one long mix test compile does not block the next issue.

## Frequently asked questions

### Does the test need Postgres?

Yes. The mix test alias in mix.exs runs ecto.create --quiet and ecto.migrate --quiet before the tests, and the songs table is an Ecto schema. The sandbox runs that alias. You do not point it at a production database. If create fails because Postgres is not reachable in the sandbox, the run stops and says so rather than skipping the test.

### Will it rewrite the owner's delete test?

It leaves the existing test, which remounts with live/3 and refutes the title. That test still proves the row is gone from the database. The new test is the one that fails when a viewer who is already mounted keeps the title. Both are in the pull request.

### What if the broadcast already works?

Then the new test passes on the first run, before any production edit. The agent does not invent a fix. The pull request contains the test and says the reported bug did not reproduce on this tree, and it quotes the passing assertion. That is a useful result. The issue stays open for whoever can supply a case the test does not cover.

### Why is PlayerLive not the playlist?

PlayerLive's SongDeleted handler only stops the player when the deleted id is the current song. The list of songs is the :songs stream on ProfileLive, updated in that view's handle_info. A test that only asserts the player stopped would miss the bug the issue describes.

### How does the Linear issue get into the sandbox?

As a resources URL, https://linear.app/<workspace>/issue/<ID>. The GitHub repository is the other resources entry, and a run takes one repository. Connect Linear with POST /me/connections {"type": "app", "name": "linear"} and open authorize_url while signed in to the console as the key's creator. A Linear sign-in cannot be named in connections yet. The issue URL is what reads it, and an issue with no Linear connection is 422 RESOURCE_NOT_ACCESSIBLE.
