# Repositories and pull requests

A cloud run given a GitHub `repository` and `ref` works on that code. The run's agent does the git and the GitHub itself, as a developer would: it commits, pushes a branch, opens a pull request when the task calls for one, watches its checks, reads the logs of a failed run, fixes, and pushes again. workbenches.dev does none of that for it, and never publishes anything on its own when a run ends.

## Connect GitHub

Connect GitHub for the repository's owner from your organization's sources page in the dashboard. It installs the workbenches.dev GitHub App, or links an installation you already have, to your organization.

- Linking an installation lets every member of the organization run on its repositories, private ones included, so you choose each installation.
- You need authority over the GitHub account: on a personal account it must be your own, and on a GitHub organization you must be an active admin of it.
- An installation already linked to an organization you do not belong to cannot be linked. One linked to another of your organizations can be reused.

The installation must grant `contents`, `pull requests`, `workflows`, `issues`, and `actions` (write), and `checks` and `statuses` (read). Without them GitHub refuses the requests the agent makes.

|                    | GitHub connected for the owner                         | Not connected                                                                                             |
| ------------------ | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| Public repository  | Clone, push its branch, open and update a pull request | Clones anonymously. Cannot push.                                                                          |
| Private repository | Clone, push its branch, open and update a pull request | Fails: `The repository could not be fetched. If it is private, connect GitHub for <owner> and run again.` |

A public repository outside the installation's repository selection is cloned anonymously, as if there were no installation.

## Start a repository run

Pass `repository` (`owner/name`) and `ref` together, and optionally `branch`:

```json
{
    "tool": "run",
    "arguments": {
        "workbench": "acme/code-review",
        "task": "Fix the failing migration test and open a draft pull request.",
        "repository": "acme/api",
        "ref": "main",
        "branch": "fix-migration-test"
    }
}
```

In `POST /v1/runs` these go in `workspace`: `{ "repository": "acme/api", "ref": "main", "branch": "fix-migration-test" }`. `ref` is resolved to a commit before the sandbox is created, so a ref GitHub refuses fails the run before anything is billed.

## The run's branch

The agent may push exactly one branch:

- `workbenches/<branch>` when you pass `branch`,
- otherwise `workbenches/<session id>`.

Every other branch, every tag, a signed push, and a compressed push are refused. Force pushes and deleting the run's own branch are allowed. Agents cannot push a name the task asks for, so name the branch with `branch`, not in the task. The agent is told which branch to push and to mention any requested name in the pull request.

A branch name is one segment, up to 100 characters, of ASCII letters, digits, and `.`, `_`, `-`. It follows `git check-ref-format`: it cannot be empty, start with `-` or `.`, end with `.` or `.lock`, contain `..`, `@{`, spaces, control characters, or any of `~ ^ : ? * [ \`, be `@`, or start with `wbs_` in any case. A name that breaks a rule is a 400 that says which.

A branch cannot be used by two unfinished runs of the same repository in one organization: the second is refused with 409 `branch_in_use`, naming the branch. Once the earlier run ends, a later run may name the same branch, which is how a follow-up carries on the work and its pull request. The repository is compared without regard to case and the branch with case, so `fix-Shared` and `fix-shared` differ.

The effective branch, prefix included, is `branch` on the run.

## How the agent reaches GitHub

No GitHub token ever enters the sandbox, not even to read. The sandbox holds one run credential that works only against workbenches.dev, which forwards each request to GitHub with an installation token scoped to the one repository. The credential is bound to the run, its organization, its repository, and its branch, and is revoked the moment the runner stops.

The runner's environment has `GH_HOST`, `GH_ENTERPRISE_TOKEN`, `GH_PROMPT_DISABLED=1`, `GH_REPO`, and `WORKBENCH_BRANCH`, so ordinary `git` and `gh` commands work. An image without `gh` gets a pinned release, downloaded from GitHub and checked against its published checksum before the runner starts. It goes in `~/.local/bin`, which comes last on the runner's `PATH`, so it needs no root and never replaces a command the image has. When the install cannot work, as in an image with no way to download files, the run still goes, `repository.ready` and the MCP `wait` result carry a warning that says why, and the agent can push its branch but cannot open or watch a pull request.

The checkout is on a local branch named for the run's branch, cut from the base commit. Commits are authored as `Workbenches <noreply@workbenches.dev>`; the person who started the run is not named.

### What the agent can do on GitHub

- Read anything in the repository.
- Open a pull request whose head is its own branch, and update, comment on, mark ready, or convert to draft that pull request.
- Re-run GitHub Actions runs and jobs of its own branch.

Everything else is refused: merges, writes to contents, refs, and commits through the API, releases, settings, branch protection, hooks, secrets, workflow dispatches, cancelling or approving workflow runs, re-running other branches' CI, comments on issues or other pull requests, and requests outside the repository. A run may make 600 requests a minute; past that GitHub requests get 429 with `Retry-After`. To run CI on new code, the agent pushes a commit to its branch.

### Protect your default branch

A run's branch can run GitHub Actions workflows, including workflows the agent adds, with the repository's secrets and a `GITHUB_TOKEN` the workflow may elevate. The rule that the agent can push only its own branch is not a boundary against Actions. Protect the default branch and tags with rulesets, and keep secrets in environments that require approval.

## The pull request

When the agent opens or updates a pull request from its branch, workbenches.dev reads it back from GitHub and records it on the run as `delivery`:

```json
{
    "state": "published",
    "branch": "workbenches/fix-migration-test",
    "base_branch": "main",
    "commit": "4f2c...",
    "pull_request": {
        "number": 128,
        "url": "https://github.com/acme/api/pull/128"
    }
}
```

The run keeps the latest, and a `delivery.completed` event is added once per pull request and head commit. A run whose agent opened none has no `delivery`. MCP `wait` reports the pull request when the run ends.

## The change

When the runner stops, workbenches.dev stages everything in the checkout (`git add -A`, so `.gitignore` applies) and measures it against the base commit, including commits the agent made. The patch is kept in the run's outcome and returned by `GET /v1/runs/:id/changes` as `text/x-diff`, whatever happened on GitHub, for cancelled and failed runs too. Apply it with `git apply`. A patch over 10 MB is not kept, and the outcome carries a `changes_too_large` warning. The MCP `changes` tool shows the files with lines added and removed, and the diff.

## Anonymous runs

A run without an installation for the owner clones with no credential. It emits only `repository.ready`, records its baseline, and keeps its change in the outcome like any other, but it cannot push and has no `delivery`.

If the GitHub connection for the owner was unlinked or replaced after the run was created, the run fails with `The GitHub connection for <owner> is no longer the one this run started with. Connect GitHub for <owner> and run again.`
