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

# Fix a GitHub issue

> Connect GitHub, give an agent write access to one repository, and run a graded session that fixes an issue and opens a pull request.

In this tutorial, an agent fixes a bug from a GitHub issue, adds a test, and opens a pull request. The session is graded, so it only completes once a grader confirms the pull request exists, the tests pass, and the fix is covered by a test. It takes about 20 minutes.

The TypeScript samples assume the `rl` client from [Client setup](/managed-agents/api#client-setup). The examples use the repository `acme/web` and its issue 412; replace them with yours.

## Before you begin

* You need the Developer or Admin role in the organization. See [Organizations and roles](/managed-agents/organizations-and-roles).
* You need permission to install a GitHub App on the GitHub account that owns the repository, or an existing GitHub connection in Recursion.
* The repository should have a test command that runs in a clean checkout, such as `npm test`.

```mermaid theme={"theme":"css-variables"}
flowchart LR
  connect["Connect GitHub"]
  env["Create an environment that can reach GitHub"]
  agent["Create an agent with write access to acme/web"]
  session["Start a graded session"]
  pr["Agent opens a pull request"]
  grade["Grader checks the pull request and tests"]
  connect --> env --> agent --> session --> pr --> grade
  grade -->|"needs revision"| pr
```

## Step 1: Connect GitHub

Connecting is an interactive authorization in GitHub, so do it in the console. It creates a connection for your organization but gives no agent access yet. The SDKs don't wrap integration operations; use the console or cURL.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Integrations**, then click **Add integration**.
    2. Choose **GitHub**, then click **Continue**.
    3. On GitHub, choose the account and select the repositories the connection may reach, including `acme/web`.
    4. Back in Recursion, choose the account if asked. The connection appears under **Integration connections**.
    5. Open the connection's actions menu and click **Check** to confirm it can issue a token.
  </Tab>

  <Tab title="cURL">
    Connect in the console first, then list connections to get the connection id you'll use in step 3.

    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/integrations/connections' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "connections": [
    {
      "connection_id": "31e6c9a4-2d75-48b0-a1f3-7c5e9d2b6a84",
      "organization_id": "cl9x2k4f1000008l5h3g7a2bq",
      "provider": "github",
      "external_id": "58213377",
      "account_login": "acme",
      "account_type": "Organization",
      "resource_selection": "selected",
      "state": "active",
      "created_at": "2026-09-25T16:40:12Z",
      "updated_at": "2026-09-25T16:40:12Z"
    }
  ]
}
```

Continue when `state` is `active`.

## Step 2: Create an environment that can reach GitHub

New environments block all outbound traffic. A GitHub grant supplies credentials, not network access, so the environment must allow GitHub's hosts. This example also allows the npm registry so the agent can install dependencies and run the tests.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Environments**, then click **Create environment**.
    2. Enter a **Name**, such as `github-fixes`.
    3. Set **Internet access** to **Enabled**.
    4. Click **Create environment**.

    The console offers all or nothing. To allow only specific hosts, use the API.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const environment = await rl.managedAgents.createEnvironment({
      body: {
        name: 'github-fixes',
        provider: 'runs',
        network_policy: {
          version: 'v1',
          rules: [
            { match: { host_glob: 'github.com' }, action: { type: 'allow' } },
            { match: { host_glob: 'api.github.com' }, action: { type: 'allow' } },
            { match: { host_glob: 'codeload.github.com' }, action: { type: 'allow' } },
            { match: { host_glob: 'objects.githubusercontent.com' }, action: { type: 'allow' } },
            { match: { host_glob: 'registry.npmjs.org' }, action: { type: 'allow' } },
          ],
        },
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/environments' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{
        "name": "github-fixes",
        "provider": "runs",
        "network_policy": {
          "version": "v1",
          "rules": [
            {"match": {"host_glob": "github.com"}, "action": {"type": "allow"}},
            {"match": {"host_glob": "api.github.com"}, "action": {"type": "allow"}},
            {"match": {"host_glob": "codeload.github.com"}, "action": {"type": "allow"}},
            {"match": {"host_glob": "objects.githubusercontent.com"}, "action": {"type": "allow"}},
            {"match": {"host_glob": "registry.npmjs.org"}, "action": {"type": "allow"}}
          ]
        }
      }'
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "organization_id": "cl9x2k4f1000008l5h3g7a2bq",
  "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
  "name": "github-fixes",
  "scope": "organization",
  "provider": "runs",
  "computer_use": false,
  "created_at": "2026-09-25T16:44:03Z",
  "updated_at": "2026-09-25T16:44:03Z"
}
```

If your tests need other hosts, such as a Python package index, add an allow rule for each. See [Environments](/managed-agents/environments).

## Step 3: Create the agent with GitHub access

Give the agent **Read and write** access (`write`) to only the repository it needs. That preset can push commits and open pull requests. The agent gets authenticated `git` and `gh` commands in its sandbox through a short-lived token. The token isn't put in the prompt, but commands in the sandbox can read it, so grant only the repositories the task needs.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Agents**, then click **Create agent** and choose **Blank**.
    2. Enter a **Name**, choose a model, and paste the system prompt from the TypeScript tab into **System prompt**. Click **Create agent**.
    3. On the agent's **Configuration** tab, under **Integrations**, click **Add integration access** and select the GitHub connection.
    4. Set **Access level** to **Read and write**.
    5. Clear **All authorized repositories**, then select `acme/web`.
    6. Click **Use this access**, then click **Save new version**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const agent = await rl.managedAgents.createAgent({
      body: {
        name: 'Issue fixer',
        model: 'anthropic/claude-sonnet-4-5',
        system: [
          'You fix GitHub issues.',
          'Clone the repository into /workspace, read the issue with gh, and reproduce the bug with a failing test.',
          'Fix the bug, run the full test suite, and push a branch named fix/issue-<number>.',
          'Open a pull request with gh whose description starts with "Fixes" and the issue URL. Never push to main.',
          'End with the pull request URL.',
        ].join('\n'),
        nativeIntegrations: [
          {
            connectionId: '31e6c9a4-2d75-48b0-a1f3-7c5e9d2b6a84',
            permission: 'write',
            resources: ['acme/web'],
          },
        ],
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/agents' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{
        "name": "Issue fixer",
        "model": "anthropic/claude-sonnet-4-5",
        "system": "You fix GitHub issues.\nClone the repository into /workspace, read the issue with gh, and reproduce the bug with a failing test.\nFix the bug, run the full test suite, and push a branch named fix/issue-<number>.\nOpen a pull request with gh whose description starts with \"Fixes\" and the issue URL. Never push to main.\nEnd with the pull request URL.",
        "nativeIntegrations": [
          {
            "connectionId": "31e6c9a4-2d75-48b0-a1f3-7c5e9d2b6a84",
            "permission": "write",
            "resources": ["acme/web"]
          }
        ]
      }'
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "agent_id": "5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55",
  "latest_agent_version_id": "c1a94e07-2f6b-4d18-b3a5-0e7d8c6f4a21",
  "name": "Issue fixer",
  "nativeIntegrations": [
    {
      "connectionId": "31e6c9a4-2d75-48b0-a1f3-7c5e9d2b6a84",
      "permission": "write",
      "resources": ["acme/web"]
    }
  ]
}
```

You can grant GitHub through a vault instead, which is useful when several agents share one grant. See [GitHub](/managed-agents/github) and [Vaults](/managed-agents/vaults).

## Step 4: Start a graded session

The outcome states what done means. The grader checks the work in the same sandbox with the same tools, so write criteria it can verify, such as a pull request it can open with `gh pr view` and tests it can run. `max_iterations` caps how many times the grader can send the work back.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Sessions**, then click **Launch session**.
    2. Choose the **Issue fixer** agent and the **github-fixes** environment.
    3. Under **Task**, select **Grade this session against a rubric**, and paste the rubric from the TypeScript tab into **Rubric**.
    4. In **Opening message**, enter `Fix https://github.com/acme/web/issues/412 and open a pull request.`
    5. Click **Launch session**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { session_id: sessionId } = await rl.managedAgents.startSession({
      'Idempotency-Key': 'acme-web-issue-412',
      body: {
        agent_id: '5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55',
        environment_id: '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13',
        outcome: {
          description: 'Fix https://github.com/acme/web/issues/412 and open a pull request.',
          rubric: [
            '- An open pull request in acme/web comes from a branch other than main, and its description contains "Fixes https://github.com/acme/web/issues/412".',
            '- The pull request adds a test that fails without the fix and passes with it.',
            '- npm test passes on the pull request branch.',
            '- The final message gives the pull request URL.',
          ].join('\n'),
          max_iterations: 3,
        },
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/sessions' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Idempotency-Key: acme-web-issue-412' \
      -H 'Content-Type: application/json' \
      -d '{
        "agent_id": "5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55",
        "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
        "outcome": {
          "description": "Fix https://github.com/acme/web/issues/412 and open a pull request.",
          "rubric": "- An open pull request in acme/web comes from a branch other than main, and its description contains \"Fixes https://github.com/acme/web/issues/412\".\n- The pull request adds a test that fails without the fix and passes with it.\n- npm test passes on the pull request branch.\n- The final message gives the pull request URL.",
          "max_iterations": 3
        }
      }'
    ```
  </Tab>
</Tabs>

The API answers `202 Accepted`. With no `message`, the outcome's description opens the session.

```json theme={"theme":"css-variables"}
{
  "session_id": "b7e1c9a4-3d62-4f15-8a07-5c2e9f6d1b38",
  "status_path": "/v1/sessions/b7e1c9a4-3d62-4f15-8a07-5c2e9f6d1b38"
}
```

The `Idempotency-Key` makes the start safe to retry: sending the same key again returns the same session instead of starting a second one.

## Step 5: Wait for the grade

Watch the session in the console, or poll the outcome until its `status` is `terminal`.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Sessions**, then open the session.
    2. Follow the transcript as the agent clones, tests, and pushes. The grader's verdicts appear as each attempt is graded.
    3. When the session completes, the last message gives the pull request URL.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    let outcome;
    do {
      await new Promise((resolve) => setTimeout(resolve, 15_000));
      const { outcomes } = await rl.managedAgents.listSessionOutcomes({ session_id: sessionId });
      outcome = outcomes?.[0];
    } while (outcome?.status !== 'terminal');
    console.log(outcome.terminal_result);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/b7e1c9a4-3d62-4f15-8a07-5c2e9f6d1b38/outcomes' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

A satisfied outcome looks like this:

```json theme={"theme":"css-variables"}
{
  "outcomes": [
    {
      "organization_id": "cl9x2k4f1000008l5h3g7a2bq",
      "session_id": "b7e1c9a4-3d62-4f15-8a07-5c2e9f6d1b38",
      "outcome_id": "e61a3f90-4c2b-4d85-9b17-3a0f8e5c2d64",
      "description": "Fix https://github.com/acme/web/issues/412 and open a pull request.",
      "rubric": "- An open pull request in acme/web comes from a branch other than main, and its description contains \"Fixes https://github.com/acme/web/issues/412\".\n- The pull request adds a test that fails without the fix and passes with it.\n- npm test passes on the pull request branch.\n- The final message gives the pull request URL.",
      "max_iterations": 3,
      "status": "terminal",
      "terminal_result": "satisfied",
      "created_at": "2026-09-25T16:47:20Z",
      "updated_at": "2026-09-25T17:09:51Z",
      "ended_at": "2026-09-25T17:09:51Z"
    }
  ]
}
```

### What success means

* The outcome's `terminal_result` is `satisfied`, the session's `execution_state` is `completed`, and its `stop_reason` is `outcome_satisfied`.
* A pull request is open in `acme/web` and every rubric criterion passed.
* Other terminal results need a look: `max_iterations_reached` means the grader still found a failing criterion after 3 attempts, and `failed` means the rubric couldn't be graded. See [Outcomes](/managed-agents/outcomes).

## Step 6: Review the pull request

Open the URL from the agent's last message, or list the repository's open pull requests through the connection.

```bash theme={"theme":"css-variables"}
curl 'https://api.recursion.labelbox.com/managed-agents/v1/integrations/connections/31e6c9a4-2d75-48b0-a1f3-7c5e9d2b6a84/repositories/web/pull-requests?state=open' \
  -H "Authorization: Bearer $RECURSION_API_KEY"
```

```json theme={"theme":"css-variables"}
{
  "page": 1,
  "pull_requests": [
    {
      "number": 418,
      "title": "Escape quotes in search queries",
      "head_sha": "3f9c2e7a1b4d6e8f0a2c4e6b8d0f1a3c5e7b9d2f",
      "draft": false
    }
  ]
}
```

Review and merge it the way you would any contributor's pull request.

## What can go wrong

| Symptom                                              | Cause                                                                                  | Fix                                                                                                                                   |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `git clone` or `gh` fails with a network error       | The environment blocks GitHub's hosts.                                                 | Allow `github.com` and `api.github.com`, plus `codeload.github.com` and `objects.githubusercontent.com` for archives and large files. |
| `git` or `gh` is not authenticated                   | The agent version has no GitHub access, or the session started from an older version.  | Add integration access, save a new version, and start a new session.                                                                  |
| The repository returns `404`                         | The GitHub App installation or the grant's repository list doesn't include `acme/web`. | Add the repository in GitHub and in the agent's integration access.                                                                   |
| `git push` or `gh pr create` is refused              | The access level is **Read only**.                                                     | Use **Read and write**.                                                                                                               |
| `npm install` fails                                  | The environment blocks the package registry.                                           | Allow `registry.npmjs.org`, or your registry's host.                                                                                  |
| The outcome ends `max_iterations_reached`            | The grader kept finding a failing criterion.                                           | Read the grader's feedback in the transcript, then send a follow-up message or loosen a criterion that can't be met.                  |
| The session stops with `stop_reason` `grader_failed` | The grader couldn't produce a verdict. The work isn't marked as failed.                | Send a follow-up message to retry grading.                                                                                            |
| The agent pushed to the wrong branch                 | The system prompt didn't forbid it strongly enough.                                    | Keep "Never push to main" in the system prompt, and protect `main` in GitHub.                                                         |

## Limits

* A grant can list up to 500 repositories.
* An agent can hold up to 25 integration connections.
* See [Limits](/managed-agents/limits) for session, outcome, and environment limits.

## Next steps

<CardGroup cols={2}>
  <Card title="GitHub" href="/managed-agents/github">
    Permission presets, vault grants, and GitHub MCP tools.
  </Card>

  <Card title="Outcomes" href="/managed-agents/outcomes">
    Write rubrics that grade reliably.
  </Card>

  <Card title="Research team tutorial" href="/managed-agents/use-cases/research-team">
    Split a larger task across a team.
  </Card>

  <Card title="Environments" href="/managed-agents/environments">
    Network policy, setup scripts, and compute.
  </Card>
</CardGroup>
