> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agent-loadout.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Reproduce CI Failures and Open Pull Requests Automatically

> Wake on a CI failure email, reproduce it on a fresh Linux machine, push a fix branch, and reply in the same email thread automatically.

Your CI pipeline already sends failure reports by email. Point those emails at the agent's address and it handles the rest: it wakes on the event, clones the repository on a fresh Linux machine, reproduces the failure, pushes a fix branch, and replies in the same email thread with a pull request link. Nothing runs on a developer's laptop, and the machine stops itself when the work is done.

## What you need

An agent equipped with an inbox and a token that has the following capabilities:

* **email:read** — to receive and read CI failure reports
* **email:send** — to reply in the thread with the result
* **compute:run** — to create and run commands on a Linux machine

Machines must be enabled in your workspace. The `compute:run` capability is feature-flagged — contact support if it is not visible on your plan.

## Steps

<Steps>
  ### Route CI mail to the agent and allowlist the sender

  Add the agent's inbox address as a recipient on your CI failure notification settings. Then add an allowlist rule for the CI sender domain so the inbox stays quiet and only legitimate failures reach the agent.

  ### Subscribe to the events stream

  Use `wait_for_message` for simple polling, or subscribe to the events stream via the REST API or SDK to receive `message.received` events as they arrive. The event payload carries IDs and a snippet — never the full body.

  ### Check the screening verdict before acting

  Read the full message with `read_message` and inspect `screening.verdict`. If the sender address does not match your CI service or the verdict is not `"clean"`, skip the message. Mail is untrusted external data — the agent must never treat it as instructions from the platform.

  ### Create a machine with a short TTL

  Call `create_machine` with `size: "small"` and a `ttl_seconds` of 1200 (20 minutes) or whatever fits your build. The whole window is reserved from the organization's allowance and returned when the machine stops early.

  ### Run the failing command

  Call `run_command` with the reproduce command. For long builds, start them in the background and poll. Read `result.exit_code` and `result.stderr` to determine whether the failure reproduced.

  ### Reply in the thread and label the conversation

  Call `reply_to_message` with the message ID to keep the response in the same email thread, then call `update_thread` to add a label (e.g. `"ci"`) and mark the conversation read for the agent.

  ### Stop the machine

  Call `stop_machine` to snapshot the disk and stop the billing clock. You can resume it later with `resume_machine` if you need to investigate further.
</Steps>

## Chat prompt example

Use this prompt in any chat client with Agent Loadout connected:

```text Chat prompt theme={null}
Read the newest CI failure mail in your inbox, reproduce it on a small machine
with a 20-minute window, and tell me the failing test and the likely cause.
Stop the machine when you are done.
```

## TypeScript example

```typescript ci-fixer.ts theme={null}
import { AgentLoadout } from "@agent-loadout/sdk";

const agent = new AgentLoadout({ apiKey: process.env.AGENT_LOADOUT_TOKEN! });
const [inbox] = await agent.inboxes.list();

for await (const event of agent.events.subscribe(inbox.id, { types: ["message.received"] })) {
  const message = await agent.messages.get(String(event.data.message_id));
  if (!message.from.address.endsWith("@ci.example") || message.screening?.verdict !== "clean") continue;

  const machine = await agent.machines.create({ size: "small", ttl_seconds: 1200, name: "ci-fix", agent_token_capabilities: ["email:read"] });
  // Poll until ready, then run the repro. The GIT_TOKEN comes from an injected vault credential, not from this script.
  const result = await agent.machines.run(machine.id, { command: "git clone https://github.com/acme/app && cd app && bun install && bun test", timeout_seconds: 600 });
  await agent.messages.reply(message.id, { text: result.exit_code === 0 ? "Passes on a clean machine." : `Reproduced:\n${result.stderr.slice(-2000)}`, idempotency_key: `ci-${message.id}` });
  await agent.threads.update(message.thread_id, { add_labels: ["ci"], read: true });
  await agent.machines.stop(machine.id);
}
```

## CLI example

```bash CLI theme={null}
agent-loadout events tail inb_… | jq -r 'select(.type=="message.received") | .data.message_id'
agent-loadout machines create --size small --ttl 1200 --name ci-fix
agent-loadout machines run mch_… --command "cd app && bun test" --timeout 600
agent-loadout machines stop mch_…
```

## Required permissions

| Capability    | Why it's needed                     |
| ------------- | ----------------------------------- |
| `email:read`  | Receive and read CI failure reports |
| `email:send`  | Reply in the thread with the result |
| `compute:run` | Create machines and run commands    |

<Tip>
  Pass `inject_credential_ids` when creating a machine to place a repository token (e.g. a GitHub PAT stored in the vault) into the machine's environment. The credential value is injected directly — it never passes through the calling script or appears in logs.
</Tip>
