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

# Repository Confinement

> Confine an agent's GitHub reach to the repositories its spawn involves — how to turn it on, what the agent can still do, and what changes for the commands it runs.

An agent's GitHub reach is normally a property of the *identity* it runs as, not of
the job it was given: a developer agent reaches every repository its owner's token
reaches, and a [service profile](/concepts/service-profiles) agent reaches every
repository its own credential does — the token in the profile's
`github_token_secret`, or your GitHub App installation when it stores none. An
agent dispatched to triage one workspace's issues holds write access to
everything else for as long as it runs.

Repository confinement closes that gap. It is an **additional** restriction, never a
grant: a confined agent's GitHub access is limited to **the repositories its spawn
already involves**, whichever credential serves it. What the agent may do inside that
set is unchanged — still bounded by the serving credential's own permissions, your
[roles and bindings](/security/authorization), and any restriction on the identity it
borrows.

## Turn it on

Pass one flag. You never list repositories — the set is derived from what the spawn
already declares, so it cannot drift from what the agent actually clones.

<CodeGroup>
  ```bash CLI theme={null}
  murmur spawn --restrict-repository-access triage-issues "Triage new issues"
  ```

  ```yaml Flight frontmatter theme={null}
  ---
  workspace: default
  service_profile: bots
  restrict_repository_access: true
  on:
    issue_opened:
      repos: ["https://github.com/acme/api"]
  ---
  ```

  ```json MCP theme={null}
  {
    "slug": "triage-issues",
    "description": "Triage new issues",
    "restrict_repository_access": true
  }
  ```
</CodeGroup>

The effective set is the [workspace's](/concepts/workspaces) repos, this spawn's own
`--repo` entries, the upstreams those entries open pull requests against, and the
workspace's plugin repos. Each one is pinned to its **stable GitHub repository id**
at spawn time, so a repository renamed later is still the same repository to the
platform — and a rename cannot bring a different one into the set.

<Note>
  Confinement is **sticky per slug**. Once an agent is restricted, later spawns of
  that slug stay restricted whether or not the flag is passed again, and nothing
  lifts it. To run the same work unconfined, use a different slug.

  The repository **set** is recomputed on every top-level spawn, so a resurrect
  picks up a repository the workspace gained in the meantime. A child agent is the
  exception: it inherits its parent's set as the parent had it pinned, so a
  repository added to the workspace afterwards is not in reach until the parent
  itself is spawned again.

  A flight on a `schedule` (cron) trigger follows the same rule at its own moment:
  each firing computes its set when it fires, from the workspace as it stands then,
  so a repository added to the workspace is in reach at the next tick without
  rewriting the flight.
</Note>

## What a confined agent can do

* **Repository access is limited to the set.** An operation naming a repository
  outside it is refused, and so is one that reaches across repositories without
  naming any (`GET /user/repos`, a global code search). Metadata and preflight
  requests that name no repository still go through, because their response carries
  no repository data.
* **The VM holds no GitHub token.** There is no `GH_TOKEN` on the machine for an
  agent — or a prompt injection — to use directly. Each operation is authorized and
  credentialed individually by the platform, through the GitHub API gateway.
* **Children inherit the set exactly.** A child agent works in its parent's
  workspace and repos, so it gets the parent's pinned set verbatim. A child cannot
  narrow it, widen it, or supply repositories of its own.

## What changes for `gh`

Most of what an agent does over `gh` is unaffected: `gh repo view`, `gh issue list`,
`gh label list`, `gh pr checks`, and the mutations that act on one item — close,
reopen, ready, review, label, comment — name one repository and touch nothing else.

Some commands ask GitHub for more than the repository they name. `gh pr view` reads
the *head repository* of a fork and the account behind it; `gh issue view` reads an
issue's parent and sub-issues, which GitHub allows to live in any repository. On
your own GitHub credential those reads are refused rather than narrowed, so the
platform provides an explicit repository-local form to ask for the part it can
serve:

```bash theme={null}
gh pr view --confined                                  # rendered for a person
gh pr view --confined --json number,title,mergeable    # your own field set
gh issue view --confined
```

* Without `--json`, the confined form requests a field set that stays inside the
  repository and prints it as text — number, title, state, author, branches, diff
  size, review decision, mergeability, labels, assignees, timestamps, URL, body.
* With `--json`, **your field set passes through exactly as given** (with `--jq` or
  `--template` if you use them), because that output feeds a program. A field that
  reaches outside the repository is refused by name rather than dropped from your
  set.

`--confined` is available on `gh pr view` and `gh issue view`. Commands whose
purpose is to look **across** an account have no repository-local form: `gh pr
status` runs two global searches, which no single-repository request answers.

### `gh pr create` needs a different route

Creating a pull request is the one write worth calling out. The create mutation
itself is repository-local, but `gh pr create` does not start there: it first
resolves the branch to any existing pull request, reads the repository network to
find the base repository, and — with `--reviewer @org/team` — looks up the team.
Those reads go to a fork's parent, the fork network, and another account, so the
command can be refused before it ever reaches the mutation.

Two routes work under confinement:

* **The repository-scoped REST endpoint**, which names one repository in its path
  and is therefore bounded by construction:

  ```bash theme={null}
  gh api repos/{owner}/{repo}/pulls -f title=… -f head=… -f base=…
  ```

* **An agent served by a GitHub App installation token** — a flight running as a
  service profile that stores no GitHub token of its own — where the pinned token
  also serves the repository-local lookups the command makes first.

Neither route covers `--reviewer @org/team`: a token pinned to a repository does not
make organization and team lookups work, and they reach outside the set on any
credential.

## Reading a refusal

A refused operation says which rule refused it and what to do instead.

| Refusal                                                                                                                                                                                                                      | What it means                                                                                                                                                                           |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<repo> is outside this agent's repository access: its spawn restricted it to …`                                                                                                                                             | The operation named a repository that is not in the set. Work in one of the listed repositories, or spawn an agent whose workspace includes the one you need.                           |
| `this agent's repository access is restricted to …: every operation must name a repository, and this request names none`                                                                                                     | The operation is account-wide (a global search, a user-level endpoint). Address one repository per request.                                                                             |
| `this agent's GitHub access is confined to a repository set, and this request's reach is not provably inside it — … at PullRequest.headRepositoryOwner … gh pr view --confined asks for the repository-local fields instead` | The request named an allowed repository but also asked for something outside it. The message names the field and, for `gh pr view` / `gh issue view`, the confined form to run instead. |

## Which credential serves a request

Confinement leaves credential selection alone — the usual precedence still
decides which credential serves. What changes is how that credential is bounded:

* **Your own GitHub credential** — a developer agent's token, or a service
  profile's stored token — serves a request whose reach is provably inside one
  repository in the set. A request reaching outside it is refused, with the
  message naming the field that put it outside and, where one exists, the
  confined form to run instead.
* **A GitHub App installation token**, which serves an agent that stores no
  GitHub credential of its own, is minted pinned to the repository the request
  names. The restriction rides on the token itself, so GitHub refuses anything
  outside the pin.

## Limits worth knowing before you turn it on

* **Every repository in the set must be visible to your GitHub App installation.**
  The set is pinned to stable ids at spawn, so a member the App cannot see rejects
  the spawn rather than producing an agent that cannot clone. The refusal names the
  repository; it came from the workspace's repos, a `--repo` entry, one of their
  pull-request upstreams, or a workspace plugin — a plugin installed from outside
  the installation is the common case.
* **Not available with `--propose`.** A staged `SpawnAction`
  [change-request](/catalog/change-request#action-change-requests) carries the flags
  listed on [`murmur spawn`](/cli/spawn), and this is not one of them.

## Related

* [Agent VM controls](/security/agent-vm-controls) — the three legs of agent VM
  security; confinement narrows the first.
* [`murmur spawn`](/cli/spawn) — the flag and its errors.
* [Flight](/catalog/flight) — the `restrict_repository_access` frontmatter field.
* [Service profiles](/concepts/service-profiles) — the identity an automated agent
  runs as.
