Skip to content

Connect a repository

After this page you can attach a private GitHub repository to a Cloud session, explain to a customer’s administrator what they need to authorize, know exactly what the sandbox holds while the agent works, and recover when that authorization changes underneath you.

A repository grant binds one exact repository to one exact app installation inside one organization. It is not an OAuth scope, not organization-wide access, and not something your app can widen at runtime.

Property Value
Identifier A UUID, opaque to you. This is what you store.
Bound to One organization, one app installation, one GitHub repository.
Operations Either ["CHECKOUT"] or ["CHECKOUT","PUBLISH"].
Uniqueness At most one active grant per installation per repository.
Mutability None. A change of scope is a new grant.

Only two operation shapes exist, and a database constraint enforces it: read-only (CHECKOUT) or read-and-publish (CHECKOUT, PUBLISH). There is no publish-only grant, because publishing an agent’s work without being able to read the base branch is not a coherent permission.

What your app ever sees of a grant is its id, the repository’s full name, default branch and private flag, and the operation list. No tokens, no clone URLs, no GitHub installation ids. The grant id is a capability handle; everything that could be used to reach GitHub directly stays on the server, and the sandbox is handed a short-lived ticket scoped to one operation instead.

The sequence is:

  1. An administrator in the organization connects their GitHub identity to WAMP Cloud, which requires them to authorize WAMP against the GitHub organizations they can act for.

  2. They choose the exact repositories your installation may use, and for each one whether it is read-only or read-and-publish. Each choice produces one grant.

  3. They hand your integration the grant ids, which you store against your own tenant record.

Practically, that means the first-run experience of your product includes a step where you tell the customer’s administrator which repositories to authorize, and then you read the result back. Plan for the case where they authorize nothing: a session with no source still works, it just has an empty workspace.

There are two read paths under /v1/repository-grants — a list and a replacement lookup — and both are installation-only: a human-delegated bearer gets 403 installation_principal_required, because a grant belongs to an installation and there is nothing coherent to return for a person.

Both calls require wamp.cloud.repositories:read. This machine-readable capability is intentionally separate from the human-only wamp.cloud.repositories:manage: an integration may discover only the grants an administrator already assigned to it, but it cannot connect, widen, or revoke repository access.

The response shape, when a caller can reach it, is the same thin projection described above:

{
"repositoryGrants": [
{
"id": "b41d0e2a-1f7c-4d3e-9a10-8c5b2f0d7e64",
"repository": { "fullName": "acme/payments-api", "defaultBranch": "main", "private": true },
"operations": ["CHECKOUT", "PUBLISH"]
}
]
}

Take the selected grant id from the list response and store it with the tenant record on your side. Discovery also provides a liveness signal for this source type:

Terminal window
curl -sS "$WAMP_API/v1/capabilities" \
-H "Authorization: Bearer $WAMP_TOKEN"
{
"capabilities": {
"repositories": {
"sources": [
{ "kind": "public_https", "available": true },
{
"kind": "github_repository_grant",
"available": true,
"discovery": "/v1/repository-grants"
}
]
}
}
}

github_repository_grant.available is true only when the calling installation actually holds at least one active grant, so it answers “may I offer this customer a repository at all?” before you render a picker. It does not tell you which repository — that is what your stored grant id is for. A human-delegated credential always sees false here, because grants belong to installations.

Pass the grant id as the session’s source when you create the session. The grant id is the only repository input the API accepts — you never send a URL, a branch ref you have not verified, or a token.

Terminal window
SESSION_ID=$(uuidgen | tr 'A-Z' 'a-z')
curl -sS -X PUT "$WAMP_API/v1/sessions/$SESSION_ID" \
-H "Authorization: Bearer $WAMP_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"task": "Add pagination to the invoices endpoint and open a draft PR.",
"title": "Invoice pagination",
"model": "claude-sonnet-5",
"source": {
"kind": "github",
"grantId": "b41d0e2a-1f7c-4d3e-9a10-8c5b2f0d7e64",
"baseBranch": "main"
}
}'
Field Rule
kind github
grantId UUID of a grant this installation holds
baseBranch Optional, 1–255 characters, no NUL / CR / LF. Defaults to the repository’s default branch.

The session response echoes a richer source than you sent, because the server resolves and pins the base at creation time:

{
"source": {
"kind": "github",
"grantId": "b41d0e2a-1f7c-4d3e-9a10-8c5b2f0d7e64",
"label": "acme/payments-api",
"private": true,
"baseBranch": "main",
"baseOid": "9f2c1b7e4a6d5c3b2a190f8e7d6c5b4a39281706"
}
}

baseOid is the exact commit the work started from. It does not move when someone pushes to main during the run, which is what makes a later publication reviewable against a fixed base. source, like task, is immutable — PATCH /v1/sessions/{sessionId} accepts only title, model and runtime.

The other two source shapes:

{ "kind": "public", "url": "https://github.com/acme/example.git", "label": "acme/example" }

A public clone needs no grant. The URL must be HTTPS (http:// is rejected), at most 2048 characters, and carry no embedded credentials. Omitting source entirely gives the session an empty workspace, which is the right choice for tasks that are not about an existing codebase.

The agent works in a real git checkout, and it never holds GitHub authority.

In the sandbox Detail
Working tree /workspace/project when the session has a source; /workspace when it does not.
Checkout Depth-1, fetched by exact object id, so the tree is baseOid and nothing else.
Branch A server-derived working branch under wamp/publications-v1/. You do not name it, and the agent does not commit onto your base branch.
Base Kept as refs/remotes/origin/<baseBranch> at baseOid, which is what a review diffs against.
origin The canonical repository URL, and credential-free.
Fetch and push Brokered through WAMP with a short-lived ticket bound to one operation, revoked as soon as that operation finishes.
GitHub token Never present. Not in the environment, not in git config, not on disk.

The consequence for your integration is worth stating plainly: an agent that goes wrong cannot push to an unrelated branch, cannot reach a repository the administrator did not grant, and cannot exfiltrate a credential that would let someone else do either. Publishing is a separate, server-mediated command — see Open a pull request.

Grants are immutable, so every scope change is a revoke plus a create. An administrator who upgrades acme/payments-api from read-only to read-and-publish leaves the grant id you stored pointing at a revoked row. Your next session creation would fail.

The intended repair path is GET /v1/repository-grants/{grantId}/replacement, which is designed exactly for this: hand it the id you stored and it tells you what that authorization is now. It sits behind the same unsettled capability as the grant list, so today it answers 403 rather than a grant — but the semantics are worth knowing, because they are what your recovery flow should assume once it is reachable:

{
"repositoryGrant": {
"id": "7c9a4f01-38be-4a15-b6d2-5e0a1c3f8d92",
"repository": { "fullName": "acme/payments-api", "defaultBranch": "main", "private": true },
"operations": ["CHECKOUT", "PUBLISH"]
}
}

The semantics are exact:

  • If the grant you named is still active, you get it back unchanged.
  • If it was revoked and the same installation now holds an active grant for the same repository that includes CHECKOUT, you get that grant. Store its id in place of the old one.
  • Otherwise repositoryGrant is null — access was withdrawn rather than changed, and the honest response to your customer is to ask the administrator to grant it again.

A null there is not an error, so do not treat it as one. Real errors from that path are 400 invalid_input for a malformed id and 403 cloud_repository_grant_required when the grant service refuses the lookup.

Until it is reachable, the recovery path is the failure itself. Creating a session against a revoked grant answers 403 cloud_repository_grant_required, and so does submitting a turn or publishing on a session whose grant lost the operation it needs. Handle that code as “this customer’s authorization changed”: surface it as a re-authorize prompt naming the repository, take the fresh grant id from the administrator, and write it over the one you stored. Do not retry the same grant id — nothing about it will become valid again.