Skip to main content
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 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, 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.
The effective set is the workspace’s 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.
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.

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:
  • 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:
  • 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.

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 carries the flags listed on murmur spawn, and this is not one of them.
  • Agent VM controls — the three legs of agent VM security; confinement narrows the first.
  • murmur spawn — the flag and its errors.
  • Flight — the restrict_repository_access frontmatter field.
  • Service profiles — the identity an automated agent runs as.