Agent: Branches
Section titled “Agent: Branches”The Branches API manages the git branches that make up an agent workspace. Each branch is an isolated git worktree with its own filesystem directory, lifecycle status, and disk usage. Use these endpoints to discover branches, inspect their state, create or delete them, sync them with a remote, and open or check pull requests.
All endpoints are scoped to a workspace. The workspace ID is supplied as a path parameter for every operation.
Branch discovery
Section titled “Branch discovery”GET /api/v1/workspaces/{workspaceID}/branches
Section titled “GET /api/v1/workspaces/{workspaceID}/branches”Returns every branch registered to the current project. Each branch is an isolated git worktree with its own directory, status, and disk usage. The list is bounded by the project’s branch count (typically fewer than 50) and is not paginated; callers receive the full array in a single response. Response objects are sanitised — start_command, last_stale_notify, and last_disk_notify are stripped.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspaceID | path | string | Yes | The workspace that owns the branches. |
Response
Section titled “Response”[ { "id": "br_01HXYZABCDEF", "path": "/var/hoody/workspaces/ws_main/branches/br_01HXYZABCDEF", "branch": "feature/add-oauth", "name": "Add OAuth provider", "status": "ready", "base_branch": "main", "base_commit": "f3a9c12d4e5b6789", "created_at": 1715000000000, "updated_at": 1715090000000 }, { "id": "br_01HXYZGHIJKL", "path": "/var/hoody/workspaces/ws_main/branches/br_01HXYZGHIJKL", "branch": "fix/null-pointer", "name": "Fix null pointer in parser", "status": "resetting", "base_branch": "main", "base_commit": "a1b2c3d4e5f60718", "created_at": 1715100000000, "updated_at": 1715100500000 }]{ "success": false, "errors": [ { "message": "Invalid project context" } ], "data": null}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PROJECT_CONTEXT | Project context is invalid | The request reached the branches router but the attached project instance could not be resolved (missing or malformed project id). | Ensure the request is routed through a valid workspace/project scope before calling this endpoint. |
{ "name": "NotFoundError", "data": { "message": "Project not found" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PROJECT_NOT_FOUND | Project record not found | The project record referenced by the current instance no longer exists on disk (deleted between instance creation and this call). | Re-open the project or select a different one; do not retry with the same stale id. |
await client.agent.branches.listBranches({ workspaceID: "ws_main"});GET /api/v1/workspaces/{workspaceID}/branches/remote
Section titled “GET /api/v1/workspaces/{workspaceID}/branches/remote”Returns the git remotes configured for the project, including provider detection, repository coordinates, and whether the stored credentials are valid.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspaceID | path | string | Yes | The workspace whose remotes should be inspected. |
Response
Section titled “Response”{ "remotes": [ { "name": "origin", "url": "git@github.com:acme/widgets.git", "provider": "github", "owner": "acme", "repo": "widgets", "authenticated": true }, { "name": "upstream", "url": "https://github.com/official/widgets.git", "provider": "github", "owner": "official", "repo": "widgets", "authenticated": false } ], "defaultRemote": "origin"}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.getRemoteInfo({ workspaceID: "ws_main"});GET /api/v1/workspaces/{workspaceID}/branches/remote-refs
Section titled “GET /api/v1/workspaces/{workspaceID}/branches/remote-refs”Fetches the set of branches and tags visible on a configured git remote via git ls-remote. The remote query parameter selects which remote to inspect; when omitted, the router uses the project’s default (origin when present). The response is a point-in-time snapshot of remote refs and is not paginated — very large mirrors may return a sizeable payload. Rate-limited per remote to avoid upstream abuse.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
remote | query | string | No | The name of the remote to inspect. Defaults to the project’s default remote. |
workspaceID | path | string | Yes | The workspace whose remote refs should be listed. |
Response
Section titled “Response”{ "branches": [ "refs/heads/main", "refs/heads/release/2024-q1", "refs/heads/feature/agent-branches" ], "tags": [ "refs/tags/v1.0.0", "refs/tags/v1.1.0" ]}{ "success": false, "errors": [ { "message": "Unsafe remote name" } ], "data": null}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_REMOTE_NAME | Remote name is unsafe or malformed | The supplied remote query parameter failed assertSafeRemoteName validation (contains disallowed characters, path separators, or shell metacharacters). | Pass a plain remote identifier such as origin or upstream; avoid spaces, slashes, and quoting. |
REMOTE_RATE_LIMITED | Remote ref listing was rate-limited | The per-remote rate limiter rejected this request because recent listings against the same remote exceeded the allowed frequency. | Back off and retry after the limiter’s cool-down window; avoid polling remote-refs in tight loops. |
{ "name": "NotFoundError", "data": { "message": "Remote 'upstream' not configured" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
REMOTE_NOT_CONFIGURED | Remote is not configured on this project | The requested remote name does not match any remote registered in the project’s git configuration. | Call GET /branches/remote first to discover configured remotes, or add the remote before listing its refs. |
await client.agent.branches.listRemoteRefs({ workspaceID: "ws_main", remote: "origin"});GET /api/v1/workspaces/{workspaceID}/branches/disk-usage
Section titled “GET /api/v1/workspaces/{workspaceID}/branches/disk-usage”Returns the on-disk size of one or all branches in the workspace. When the id query parameter is omitted, the response contains an entry for every branch.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | query | string | No | The branch id to measure. Omit to measure every branch in the workspace. |
workspaceID | path | string | Yes | The workspace whose branches should be measured. |
Response
Section titled “Response”[ { "id": "br_01HXYZABCDEF", "directory": "/var/hoody/workspaces/ws_main/branches/br_01HXYZABCDEF", "bytes": 482310984 }, { "id": "br_01HXYZGHIJKL", "directory": "/var/hoody/workspaces/ws_main/branches/br_01HXYZGHIJKL", "bytes": 15728329 }]{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.getBranchDiskUsage({ workspaceID: "ws_main", id: "br_01HXYZABCDEF"});Branch status and diff
Section titled “Branch status and diff”GET /api/v1/workspaces/{workspaceID}/branches/{id}/status
Section titled “GET /api/v1/workspaces/{workspaceID}/branches/{id}/status”Returns the live git status of a single branch — its name, how far ahead or behind its upstream it is, the count of staged/unstaged/untracked files, and a clean flag.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
Response
Section titled “Response”{ "branch": "feature/add-oauth", "ahead": 3, "behind": 0, "staged": 4, "unstaged": 1, "untracked": 0, "clean": false}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.getBranchStatus({ workspaceID: "ws_main", id: "br_01HXYZABCDEF"});GET /api/v1/workspaces/{workspaceID}/branches/{id}/diff
Section titled “GET /api/v1/workspaces/{workspaceID}/branches/{id}/diff”Returns the diff between the branch and its base (or another reference). The format parameter controls whether the response is a summary or full hunk data; the file parameter scopes the diff to a single path.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
base | query | string | No | The base ref to diff against. Defaults to the branch’s base branch. |
file | query | string | No | Restrict the diff to a single file path. |
format | query | string | No | Output format. One of summary, full. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
Response
Section titled “Response”{ "base_commit": "f3a9c12d4e5b6789", "head_commit": "9b8c7d6e5f4a3210", "files": [ { "path": "src/oauth/provider.ts", "status": "modified", "insertions": 42, "deletions": 7, "hunks": [ { "old_start": 12, "old_lines": 5, "new_start": 12, "new_lines": 8, "lines": [ { "type": "context", "content": "import { Client } from './client';" }, { "type": "remove", "content": "const TOKEN_TTL = 3600;" }, { "type": "add", "content": "const TOKEN_TTL_MS = 60 * 60 * 1000;" }, { "type": "context", "content": "" } ] } ] } ], "truncated": false}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.getBranchDiff({ workspaceID: "ws_main", id: "br_01HXYZABCDEF", format: "full"});GET /api/v1/workspaces/{workspaceID}/branches/{id}/remote-status
Section titled “GET /api/v1/workspaces/{workspaceID}/branches/{id}/remote-status”Returns whether the branch has an upstream tracking ref and, if so, how many commits it is ahead or behind.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
Response
Section titled “Response”{ "hasUpstream": true, "ahead": 3, "behind": 1, "remoteBranch": "origin/feature/add-oauth"}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.getRemoteStatus({ workspaceID: "ws_main", id: "br_01HXYZABCDEF"});GET /api/v1/workspaces/{workspaceID}/branches/{id}/pr
Section titled “GET /api/v1/workspaces/{workspaceID}/branches/{id}/pr”Returns the current state of a pull/merge request for the branch — whether one exists, its URL and number, its provider state, CI status, and review status.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
Response
Section titled “Response”{ "exists": true, "url": "https://github.com/acme/widgets/pull/42", "number": 42, "state": "open", "ci": { "status": "success", "url": "https://github.com/acme/widgets/actions/runs/123456" }, "reviewStatus": "approved"}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.getPRStatus({ workspaceID: "ws_main", id: "br_01HXYZABCDEF"});Branch lifecycle
Section titled “Branch lifecycle”POST /api/v1/workspaces/{workspaceID}/branches
Section titled “POST /api/v1/workspaces/{workspaceID}/branches”Creates a new branch as an isolated git worktree. The new branch can be given a name, a base branch to fork from, and an optional start command that runs after the worktree is provisioned.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspaceID | path | string | Yes | The workspace in which to create the branch. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
name | string | No | The human-readable display name for the branch. |
startCommand | string | No | A command to run inside the new worktree once it is provisioned. |
baseBranch | string | No | The branch to fork from. Defaults to the project’s default branch. |
Response
Section titled “Response”{ "id": "br_01HMNOPQRSTU", "path": "/var/hoody/workspaces/ws_main/branches/br_01HMNOPQRSTU", "branch": "feature/billing-portal", "name": "Billing portal MVP", "status": "creating", "base_branch": "main", "base_commit": "f3a9c12d4e5b6789", "created_at": 1715200000000, "updated_at": 1715200000000}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.createBranch({ workspaceID: "ws_main", data: { name: "Billing portal MVP", baseBranch: "main", startCommand: "pnpm install && pnpm dev" }});PATCH /api/v1/workspaces/{workspaceID}/branches/{id}
Section titled “PATCH /api/v1/workspaces/{workspaceID}/branches/{id}”Renames the branch’s human-readable display name. The underlying git branch ref is not changed.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The new display name for the branch. |
Response
Section titled “Response”{ "id": "br_01HXYZABCDEF", "path": "/var/hoody/workspaces/ws_main/branches/br_01HXYZABCDEF", "branch": "feature/add-oauth", "name": "Add OAuth provider (rev 2)", "status": "ready", "base_branch": "main", "base_commit": "f3a9c12d4e5b6789", "created_at": 1715000000000, "updated_at": 1715210000000}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.renameBranch({ workspaceID: "ws_main", id: "br_01HXYZABCDEF", data: { name: "Add OAuth provider (rev 2)" }});DELETE /api/v1/workspaces/{workspaceID}/branches/{id}
Section titled “DELETE /api/v1/workspaces/{workspaceID}/branches/{id}”Deletes a branch and its underlying git worktree.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
This endpoint takes no parameters beyond the path above.
Response
Section titled “Response”{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.deleteBranch({ workspaceID: "ws_main", id: "br_01HXYZABCDEF"});POST /api/v1/workspaces/{workspaceID}/branches/{id}/reset
Section titled “POST /api/v1/workspaces/{workspaceID}/branches/{id}/reset”Resets a branch back to its base commit. While the reset is in progress the branch’s status is set to resetting; once complete it returns to ready.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
This endpoint takes no parameters beyond the path above.
Response
Section titled “Response”{ "id": "br_01HXYZABCDEF", "path": "/var/hoody/workspaces/ws_main/branches/br_01HXYZABCDEF", "branch": "feature/add-oauth", "name": "Add OAuth provider", "status": "resetting", "base_branch": "main", "base_commit": "f3a9c12d4e5b6789", "created_at": 1715000000000, "updated_at": 1715300000000}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.resetBranch({ workspaceID: "ws_main", id: "br_01HXYZABCDEF"});POST /api/v1/workspaces/{workspaceID}/branches/{id}/retry
Section titled “POST /api/v1/workspaces/{workspaceID}/branches/{id}/retry”Retries a branch whose provisioning or operation previously failed. Only branches in the failed state are eligible.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
This endpoint takes no parameters beyond the path above.
Response
Section titled “Response”{ "id": "br_01HXYZGHIJKL", "path": "/var/hoody/workspaces/ws_main/branches/br_01HXYZGHIJKL", "branch": "fix/null-pointer", "name": "Fix null pointer in parser", "status": "creating", "base_branch": "main", "base_commit": "a1b2c3d4e5f60718", "created_at": 1715100000000, "updated_at": 1715310000000}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.retryBranch({ workspaceID: "ws_main", id: "br_01HXYZGHIJKL"});Branch sync and pull requests
Section titled “Branch sync and pull requests”POST /api/v1/workspaces/{workspaceID}/branches/{id}/pull
Section titled “POST /api/v1/workspaces/{workspaceID}/branches/{id}/pull”Pulls the latest commits from the branch’s upstream (or a specified remote) into the local worktree. Returns any conflict paths if the pull cannot be fast-forwarded.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
This endpoint accepts an empty body.
Response
Section titled “Response”{ "success": true, "conflicts": [], "message": "Already up to date."}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.pullBranch({ workspaceID: "ws_main", id: "br_01HXYZABCDEF"});POST /api/v1/workspaces/{workspaceID}/branches/{id}/push
Section titled “POST /api/v1/workspaces/{workspaceID}/branches/{id}/push”Pushes the branch to its remote. When successful the response includes the pushed ref and the remote name. This endpoint is rate-limited; callers that exceed the limit receive a 429 response with a retryAfterMs hint.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
This endpoint accepts an empty body.
Response
Section titled “Response”{ "success": true, "ref": "refs/heads/feature/add-oauth", "remote": "origin", "url": "https://github.com/acme/widgets", "message": "Branch pushed successfully."}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}{ "error": "Rate limit exceeded", "retryAfterMs": 30000}await client.agent.branches.pushBranch({ workspaceID: "ws_main", id: "br_01HXYZABCDEF"});POST /api/v1/workspaces/{workspaceID}/branches/{id}/merge
Section titled “POST /api/v1/workspaces/{workspaceID}/branches/{id}/merge”Merges the branch into its base. The strategy field selects squash, rebase, or a true merge commit. Set dry_run to validate the merge without applying it, and deleteBranch to remove the source branch on success.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id to merge. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
strategy | string | No | Merge strategy. One of squash, rebase, merge. |
message | string | No | Custom commit message. |
dry_run | boolean | No | When true, validates the merge without applying it. |
deleteBranch | boolean | No | When true, deletes the source branch on a successful merge. |
Response
Section titled “Response”{ "success": true, "sha": "9b8c7d6e5f4a3210", "head_commit": "9b8c7d6e5f4a3210", "already_merged": false, "target": "main", "conflicts": [], "message": "Merge successful."}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.mergeBranch({ workspaceID: "ws_main", id: "br_01HXYZABCDEF", data: { strategy: "squash", message: "feat: add OAuth provider", deleteBranch: true }});POST /api/v1/workspaces/{workspaceID}/branches/{id}/pr
Section titled “POST /api/v1/workspaces/{workspaceID}/branches/{id}/pr”Opens a pull (or merge) request from the branch against its target. The input body is empty by contract — the PR is created from the branch’s current state.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The branch id. |
workspaceID | path | string | Yes | The workspace that owns the branch. |
This endpoint accepts an empty body.
Response
Section titled “Response”{ "success": true, "url": "https://github.com/acme/widgets/pull/43", "number": 43, "provider": "github", "message": "Pull request opened."}{ "success": false, "errors": [ { "message": "Invalid request" } ], "data": null}{ "name": "NotFoundError", "data": { "message": "Project not found" }}await client.agent.branches.createPR({ workspaceID: "ws_main", id: "br_01HXYZABCDEF"});