Agent: GitHub
Section titled “Agent: GitHub”Authenticate to GitHub through the device flow or a personal access token (PAT), clone repositories, stage and commit, sync with the remote, list repositories and branches, check the working-tree status, and open pull requests. The credential store is realm-scoped: the gateway injects its own realm identity server-side so the accounts you see belong to this realm. Per-request realm selection is unsupported on these routes; supplying X-Hoody-Realm or ?realm= returns 400 realm_scope_unsupported.
Operations that run git resolve the repository from the X-Hoody-Cwd, X-Hoody-Container, and X-Hoody-Config-Dir scope headers. Operations that talk to the GitHub REST API (github.listPrs, github.listIssues, github.listRepos) do not require a cwd but use the active account’s token. The token lives only in env or in the login validation step and is never returned on any response.
The agent host for these operations is https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com. Call the agent through this URL: Hoody Kit programs are reached through their URLs only, and a request refused by the Source IP Guard gets 403 forbidden.
Authentication
Section titled “Authentication”Get auth status
Section titled “Get auth status”Reports the GitHub authentication state for the gateway’s realm. Token is held only in env and is never returned. Per-request realm selection is unsupported, so sending X-Hoody-Realm or ?realm= returns 400 realm_scope_unsupported. This route has no cwd dimension.
GET /api/v1/agent/github/auth/status
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
curl -X GET https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/auth/status \ -H "Authorization: Bearer $HOODY_TOKEN"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.getAuth();{ "status": "ok", "accounts": [ { "key": "github.com/octocat", "login": "octocat", "active": true } ]}Verbatim github.auth.status daemon reply.
This endpoint takes no parameters.
Response statuses
Section titled “Response statuses”{ "status": "ok", "accounts": [ { "key": "github.com/octocat", "login": "octocat", "active": true } ]}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only RPC. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_store_corrupt", "message": "The GitHub credential store is unreadable (corrupt). Link an account again to reset it."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (busy or stream concurrency cap hit). | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory (permissions, disk), then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Start a login
Section titled “Start a login”Begins a GitHub login. With no body token this starts a device flow and returns the non-secret verification fields. With a body token (a PAT) it validates and persists the token directly. For GitHub Enterprise set body host (default github.com).
POST /api/v1/agent/github/auth/login
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/auth/login \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.login({}, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "device_code": "abc123", "user_code": "WXYZ-1234", "verification_uri": "https://github.com/login/device", "interval": 5, "expires_in": 900}Device flow reply. With a PAT the reply is {key, login, host}.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted means local). |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
token | string | No | Optional PAT. When present the login validates and persists this token (no device flow). Kept in env, never returned. |
host | string | No | GitHub host for GitHub Enterprise (GHES); defaults to github.com. Must match the host on the subsequent poll call. |
activate | boolean | No | Whether the linked account becomes the ACTIVE one. Defaults to false. On the PAT branch the account is linked and activated in one call. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "device_code": "abc123", "user_code": "WXYZ-1234", "verification_uri": "https://github.com/login/device", "interval": 5, "expires_in": 900}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The request was refused by the server’s admin gate. | Run the daemon with socket auth (--auth-token-path) and no separate admin capability so its gate allows HTTP requests forwarded from the gateway. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "github_token_rejected", "message": "GitHub rejected the submitted token. Check that it is a valid, unexpired token for github.com."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_token_rejected | Submitted token rejected | GitHub rejected the token submitted to this login (HTTP 401/403 on validation). | Submit a valid, unexpired token for the host. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Poll a device-flow login
Section titled “Poll a device-flow login”Polls an in-flight GitHub device-flow login. Forward the {device_code, interval, expires_in} the start reply returned, plus activate if this account should become the active one. The call blocks until the user authorizes (or the flow expires/cancels), then validates and persists the resulting token. Token lives only in env and is never returned.
POST /api/v1/agent/github/auth/login/poll
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/auth/login/poll \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"device_code":"abc123","interval":5,"expires_in":900,"activate":true}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.pollLogin({ device_code: 'abc123', interval: 5, expires_in: 900, activate: true }, { XHoodyCwd: '/workspace/my-repo' });{ "key": "github.com/octocat", "login": "octocat", "host": "github.com"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
device_code | string | Yes | The device_code returned by POST /github/auth/login. |
host | string | No | The GitHub host (default github.com); must match the start call. |
interval | integer | No | The poll interval (seconds) the start reply returned. |
expires_in | integer | No | The device-code lifetime (seconds) the start reply returned. |
activate | boolean | No | Whether the linked account becomes the ACTIVE one. Defaults to false. |
Response statuses
Section titled “Response statuses”{ "key": "github.com/octocat", "login": "octocat", "host": "github.com"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The request was refused by the server’s admin gate. | Run the daemon with socket auth (--auth-token-path) and no separate admin capability so its gate allows HTTP requests forwarded from the gateway. |
github_login_denied | Login declined | The user declined the device-flow authorization. | Start the login again (github.login). Never restart automatically after a declined authorization. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_login_expired", "message": "This GitHub login attempt expired before it was authorized. Start the login again."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_login_expired | Login attempt expired | The device-flow login expired before it was authorized. | Start the login again (github.login). Never restart automatically after a declined authorization. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "github_token_rejected", "message": "GitHub rejected the submitted token. Check that it is a valid, unexpired token for github.com."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_token_rejected | Submitted token rejected | GitHub rejected the token submitted to this login (HTTP 401/403 on validation). | Submit a valid, unexpired token for the host. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Remove a linked account
Section titled “Remove a linked account”Forgets a linked GitHub account and deletes the copies of its token that an interactive clone persisted into checkouts the daemon can currently reach. The purge is best-effort: the reply carries credential_purge:"partial" and a message saying to revoke the token on GitHub for a complete removal. If the removed account was active, the active pointer moves to a remaining account (or clears).
POST /api/v1/agent/github/auth/logout
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/auth/logout \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"key":"github.com/octocat"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.logout({ key: 'github.com/octocat' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "accounts": [], "credential_purge": "partial", "message": "Revoke the token on GitHub to remove the rest."}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Account handle from github.getAuth accounts[].key, e.g. "github.com/octocat". |
Response statuses
Section titled “Response statuses”{ "status": "ok", "accounts": [], "credential_purge": "partial", "message": "Revoke the token on GitHub to remove the rest."}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The request was refused by the server’s admin gate. | Run the daemon with socket auth (--auth-token-path) and no separate admin capability so its gate allows HTTP requests forwarded from the gateway. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_store_corrupt", "message": "The GitHub credential store is unreadable (corrupt). Link an account again to reset it."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Switch the active GitHub account
Section titled “Switch the active GitHub account”Points every subsequent GitHub operation at an already-linked account. key is the account handle from github.getAuth accounts[].key. Returns 400 when the key names no linked account.
POST /api/v1/agent/github/auth/active
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/auth/active \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"key":"github.com/octocat"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.useAccount({ key: 'github.com/octocat' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "accounts": [ { "key": "github.com/octocat", "login": "octocat", "active": true } ]}Refreshed secret-free account list with the new active one marked.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Account handle from github.getAuth accounts[].key, e.g. "github.com/octocat". |
Response statuses
Section titled “Response statuses”{ "status": "ok", "accounts": [ { "key": "github.com/octocat", "login": "octocat", "active": true } ]}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The request was refused by the server’s admin gate. | Run the daemon with socket auth (--auth-token-path) and no separate admin capability so its gate allows HTTP requests forwarded from the gateway. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_store_corrupt", "message": "The GitHub credential store is unreadable (corrupt). Link an account again to reset it."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Repository identity
Section titled “Repository identity”List GitHub repos
Section titled “List GitHub repos”Lists known or configured repos for the gateway’s realm. Returns the daemon reply verbatim ({status, repos, account}); not paginated. X-Hoody-Cwd, X-Hoody-Container, and X-Hoody-Config-Dir are threaded. Per-request realm selection is unsupported.
GET /api/v1/agent/github/repos
curl -X GET https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/repos \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "X-Hoody-Cwd: /workspace/my-repo"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.listRepos({ XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "account": "github.com/octocat", "repos": [ { "name": "Hello-World", "full_name": "octocat/Hello-World", "private": false } ]}Verbatim github.repo.list daemon reply.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "account": "github.com/octocat", "repos": [ { "name": "Hello-World", "full_name": "octocat/Hello-World", "private": false } ]}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "github_token_forbidden", "message": "GitHub refused this request for the linked token (github.com). The token lacks access to this repository or action: grant it the needed permission or repository access (fine-grained token), the needed scope (classic token), or authorize it for the organization (SAML SSO). Linking the same token again will not help."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_token_forbidden | GitHub refused the token for this request | A missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). | Grant the token access, then retry. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
github_not_found | Not found on GitHub | GitHub answered 404. | Check the owner and name. If it exists, check that the linked token can see it. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token (expired or revoked). | Link the account again with a new token and activate:true. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Resolve repository identity
Section titled “Resolve repository identity”Resolves which GitHub repository the requesting cwd’s checkout belongs to. This is the bridge between the cwd-scoped surface and the REST lists (github.listPrs, github.listIssues); call this first to obtain owner and repo. Needs a linked account and a checkout whose remote resolves to a GitHub repository. Per-request realm selection is unsupported.
GET /api/v1/agent/github/identity
curl -X GET https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/identity \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "X-Hoody-Cwd: /workspace/my-repo"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.resolveRepo({ XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "owner": "octocat", "repo": "Hello-World", "full_name": "octocat/Hello-World"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "owner": "octocat", "repo": "Hello-World", "full_name": "octocat/Hello-World"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "github_token_forbidden", "message": "GitHub refused this request for the linked token (github.com). The token lacks access to this repository or action: grant it the needed permission or repository access (fine-grained token), the needed scope (classic token), or authorize it for the organization (SAML SSO). Linking the same token again will not help."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_token_forbidden | GitHub refused the token for this request | A missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). | Grant the token access, then retry. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token (expired or revoked). | Link the account again with a new token and activate:true. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: not a git repository (or any of the parent directories): .git", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git or gh command ran in the repository and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Working tree
Section titled “Working tree”Working-tree status
Section titled “Working-tree status”Returns the git working-tree status for the requesting cwd. Scoped by X-Hoody-Cwd, X-Hoody-Container, and X-Hoody-Config-Dir. The realm identity is injected server-side; per-request realm selection is unsupported.
GET /api/v1/agent/github/status
curl -X GET https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/status \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "X-Hoody-Cwd: /workspace/my-repo"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.getStatus({ XHoodyCwd: '/workspace/my-repo' });{ "ahead": 2, "behind": 0, "branch": "main", "changed": 3, "clean": false, "cloned": true, "conflicts": 0, "detached": false, "dirty": true, "status": "ok", "untracked": 1}Verbatim github.status daemon reply.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git/gh command ran in the repository and exited non-zero (a rejected switch, a stash pop conflict, an already-existing branch, a refused checkout). It is NOT a malformed request and NOT a transport failure — the repository itself refused the operation. details.output carries git’s own token-redacted output and details.exit_code its exit status. A failure CLASSIFIED as an account problem never arrives here: it answers with its github_* code (github_token_invalid, github_token_forbidden, …). An unclassified failure may carry details.account_observation — the linked token was found dead while checking, without proof it caused this failure. | Read details.output — it names the git-level cause. Resolve it in the repository and retry. If details.account_observation is present, also check the linked account. |
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Response statuses
Section titled “Response statuses”{ "ahead": 2, "behind": 0, "branch": "main", "changed": 3, "clean": false, "cloned": true, "conflicts": 0, "detached": false, "dirty": true, "status": "ok", "untracked": 1}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: not a git repository (or any of the parent directories): .git", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git or gh command ran in the repository and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Read the working-tree diff
Section titled “Read the working-tree diff”Returns the requesting cwd’s repository diff as one unified-diff string. staged=true returns the index diff (what a commit would record); default is the unstaged working-tree diff. Runs with color and external diff drivers disabled. Captured output is capped at 1 MiB; a clamp appends …(output truncated). A clean tree yields an empty diff string, which is a successful read.
GET /api/v1/agent/github/diff
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/diff?staged=false" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "X-Hoody-Cwd: /workspace/my-repo"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.diff({ staged: false, XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "diff": "diff --git a/README.md b/README.md\nindex 3a4b5c6..7d8e9f0 100644\n--- a/README.md\n+++ b/README.md\n@@ -1,1 +1,1 @@\n-Hello\n+Hello, world\n"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
staged | query | boolean | No | When true, return the staged (index) diff. Defaults to false. Accepts true/1/yes/on; every other value, including empty, is false. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "diff": "diff --git a/README.md b/README.md\n..."}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: bad object", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Read recent commit history
Section titled “Read recent commit history”Returns the 100 most recent commits (fixed, not caller-adjustable). Signature verification and color are disabled. A repository with no commits yet (unborn HEAD) returns an empty commits list, not an error.
GET /api/v1/agent/github/log
curl -X GET https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/log \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "X-Hoody-Cwd: /workspace/my-repo"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.listCommits({ XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "commits": [ { "sha": "3a4b5c6", "subject": "Add login form", "author": "Octocat", "age": "3 days ago" } ]}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "commits": [ { "sha": "3a4b5c6", "subject": "Add login form", "author": "Octocat", "age": "3 days ago" } ]}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: your current branch 'main' does not have any commits yet", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
List linked worktrees
Section titled “List linked worktrees”Lists every worktree attached to the requesting cwd’s repository. The main checkout is included. Runs tokenless.
GET /api/v1/agent/github/worktrees
curl -X GET https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/worktrees \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "X-Hoody-Cwd: /workspace/my-repo"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.listWorktrees({ XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "worktrees": [ { "path": "/workspace/my-repo", "branch": "main", "detached": false, "bare": false, "locked": false } ]}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "worktrees": [ { "path": "/workspace/my-repo", "branch": "main", "detached": false, "bare": false, "locked": false } ]}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: not a git repository (or any of the parent directories): .git", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Add a linked worktree
Section titled “Add a linked worktree”Adds a linked worktree of the requesting cwd’s repository at path. branch creates a new branch (-b); omit it to check out an existing ref. start_ref defaults to current HEAD. The path is validated lexically and against the session deny list. To remove a linked worktree, see Remove a linked worktree.
POST /api/v1/agent/github/worktrees
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/worktrees \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"path":"/workspace/my-repo.feature","branch":"feature","start_ref":"main"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.createWorktree({ path: '/workspace/my-repo.feature', branch: 'feature', start_ref: 'main' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Preparing worktree (new branch 'feature')\n..."}Verbatim github.worktree.add exec reply.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
path | string | Yes | Where to create the worktree. Lexically validated and checked against the session deny list. |
branch | string | No | Optional new branch to create for this worktree (git’s -b). |
start_ref | string | No | Optional ref the worktree starts from; defaults to current HEAD. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Preparing worktree (new branch 'feature')\n..."}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: '/workspace/my-repo.feature' already exists", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Remove a linked worktree
Section titled “Remove a linked worktree”Removes a linked worktree of the requesting cwd’s repository (git worktree remove): its directory is deleted and git forgets it. DESTRUCTIVE, with NO confirmation round: calling this route is the intent (a body confirmed is ignored; the gateway confirms server-side). Without force, git refuses a worktree with uncommitted or untracked changes, or a locked one, and that refusal arrives as 502 git_command_failed with the tree untouched. With force:true those changes are DISCARDED. The main checkout can never be removed (400), and the path is lexically validated and checked against the session’s deny list before git runs (400). Branches are not deleted.
POST /api/v1/agent/github/worktrees/remove
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/worktrees/remove \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"path":"/workspace/my-repo.feature","force":false}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.deleteWorktree({ path: '/workspace/my-repo.feature', force: false }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": ""}Verbatim github.worktree.remove exec reply.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
path | string | Yes | The worktree’s path, as github.listWorktrees reports it. Must not be the main checkout. |
force | boolean | No | Removes the worktree EVEN WITH uncommitted or untracked changes (git worktree remove --force), DISCARDING them; they are not stashed and cannot be recovered. Defaults to false. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": ""}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters, including an unsafe worktree path or the main checkout. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: '/workspace/my-repo.feature' is a main working tree", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Stash the working tree
Section titled “Stash the working tree”Saves the requesting cwd’s working-tree changes onto the stash and restores the tree to HEAD. Includes untracked files (--include-untracked), so files that were never added to git are moved out of the working directory. A clean tree produces no stash entry.
POST /api/v1/agent/github/stash
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/stash \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.pushStash({}, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Saved working directory and index state WIP on main: 3a4b5c6 Add login form"}Verbatim github.stash.push exec reply.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”The body schema is empty. Send {}.
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Saved working directory and index state WIP on main: 3a4b5c6 Add login form"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: unmerged paths in the working tree", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Restore the most recent stash entry
Section titled “Restore the most recent stash entry”Applies the most recent stash entry back onto the requesting cwd’s working tree and drops it (always stash@{0}). A clean apply consumes the entry. An apply that conflicts leaves conflict markers and keeps the entry; that arrives as 502 git_command_failed.
POST /api/v1/agent/github/stash/pop
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/stash/pop \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.popStash({}, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "On branch main\nChanges restored to working directory and index."}Verbatim github.stash.pop exec reply.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”The body schema is empty. Send {}.
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "On branch main\nChanges restored to working directory and index."}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 1, "output": "CONFLICT (content): Merge conflict in README.md", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero, including a stash pop conflict. | Read details.output for the cause. Resolve the conflicts in the working tree and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Branches
Section titled “Branches”List GitHub branches
Section titled “List GitHub branches”Lists branches for the requesting cwd’s repo. Returns {status, branches} verbatim; not paginated.
GET /api/v1/agent/github/branches
curl -X GET https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/branches \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "X-Hoody-Cwd: /workspace/my-repo"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.listBranches({ XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "branches": [ { "name": "main", "current": true }, { "name": "feature", "current": false } ]}Verbatim github.branch.list daemon reply.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git/gh command ran in the repository and exited non-zero (a rejected switch, a stash pop conflict, an already-existing branch, a refused checkout). It is NOT a malformed request and NOT a transport failure — the repository itself refused the operation. details.output carries git’s own token-redacted output and details.exit_code its exit status. A failure CLASSIFIED as an account problem never arrives here: it answers with its github_* code (github_token_invalid, github_token_forbidden, …). An unclassified failure may carry details.account_observation — the linked token was found dead while checking, without proof it caused this failure. | Read details.output — it names the git-level cause. Resolve it in the repository and retry. If details.account_observation is present, also check the linked account. |
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "branches": [ { "name": "main", "current": true }, { "name": "feature", "current": false } ]}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Create a branch
Section titled “Create a branch”Creates a branch at the current HEAD and switches to it (git switch -c). The new branch has no upstream, so the first push must ask for one explicitly. The branch name is validated against the safe-ref rules.
POST /api/v1/agent/github/branch
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/branch \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"branch":"feature"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.createBranch({ branch: 'feature' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Switched to a new branch 'feature'"}Verbatim github.branch.create exec reply.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
branch | string | Yes | The new branch name. Validated as a safe ref name. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Switched to a new branch 'feature'"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters, including an unsafe branch name. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: a branch named 'feature' already exists", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero, including an already-existing branch or a switch blocked by local changes. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Switch to an existing branch
Section titled “Switch to an existing branch”Switches the requesting cwd’s repository to an existing branch (git switch). It never creates one and never forces: a switch that would overwrite uncommitted local changes is refused. The branch name is validated as a safe ref name.
POST /api/v1/agent/github/branch/switch
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/branch/switch \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"branch":"main"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.useBranch({ branch: 'main' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Switched to branch 'main'"}Verbatim github.branch.switch exec reply.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
branch | string | Yes | An existing branch name. Validated as a safe ref name. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Switched to branch 'main'"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters, including an unsafe branch name. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 1, "output": "error: Your local changes to the following files would be overwritten by checkout", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero, including a switch blocked by local changes. | Stash first (github.pushStash) if that is what you meant, then retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Force-delete a local branch
Section titled “Force-delete a local branch”Deletes a LOCAL branch in the requesting cwd’s repository with git branch -D. FORCED: it deletes the branch even when its commits are merged nowhere, and those commits are then reachable only through the reflog until git garbage-collects them. The remote branch is not touched. A name that is not a safe git ref name (an option such as -D, a .. sequence) is refused with 400 before git runs. DESTRUCTIVE, with NO confirmation round: calling this route is the intent (a body confirmed is ignored; the gateway confirms server-side). Deleting the currently checked-out branch is refused by git and arrives as 502 git_command_failed.
POST /api/v1/agent/github/branch/delete
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/branch/delete \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"branch":"feature"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.deleteBranch({ branch: 'feature' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Deleted branch feature (was 3a4b5c6)."}Verbatim github.branch.delete exec reply.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
branch | string | Yes | The local branch name, as github.listBranches reports it. Passed after --, so it is never read as an option. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Deleted branch feature (was 3a4b5c6)."}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters, including an unsafe branch name. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "error: Cannot delete branch 'feature' checked out at '/workspace/my-repo'", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero, including the refusal to delete the currently checked-out branch. | Read details.output for the cause. Switch to another branch first, then retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Remote sync
Section titled “Remote sync”Clone a GitHub repository
Section titled “Clone a GitHub repository”Clones a GitHub repository. The service translates repo (owner/name or an https github URL) into the server-authoritative {full_name, clone_url}. The destination is derived traversal-safe under the managed clone root; dir overrides it. Destructive.
POST /api/v1/agent/github/clone
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/clone \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"repo":"octocat/Hello-World"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.clone({ repo: 'octocat/Hello-World' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "repo_dir": "/workspace/Hello-World", "reused": false, "credentials_persisted": true, "credentials_error": ""}The clone outcome. reused:true means an existing checkout of the same repository was already at the destination and was reused instead of cloned. credentials_persisted and credentials_error appear only when the request set persist_credentials:true.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
repo | string | No | The repository to clone: "owner/name" or an https github URL. Translated to full_name + clone_url against the active account host. Supply this OR the canonical full_name+clone_url; if both, the canonical fields win. |
dir | string | No | Optional managed clone root override; the traversal-safe parent/dest are derived under it. |
clone_root | string | No | Canonical form of dir; wins when both are sent. |
full_name | string | No | Canonical "owner/name" (alternative to repo; takes precedence over a derived value). Requires clone_url. |
clone_url | string | No | Canonical https clone URL (alternative to repo; re-validated against the active account host; takes precedence over a derived value). Requires full_name. |
shallow | boolean | No | Shallow clone. Defaults to true. |
flat | boolean | No | When true, clones to <root>/<name> instead of <root>/<owner>/<name>. An existing checkout at that path is reused only when its origin is the same repository; anything else there is refused. Defaults to false. |
persist_credentials | boolean | No | When true, writes the account token into a git credential file inside the new checkout’s .git and points its credential.helper at it, so plain git push/pull in that checkout authenticates as this account. The token is then ON DISK, readable by anything running as the exec user. Defaults to false. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "repo_dir": "/workspace/Hello-World", "reused": false, "credentials_persisted": true, "credentials_error": ""}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The request was refused by the server’s admin gate. | Run the daemon with socket auth (--auth-token-path) and no separate admin capability so its gate allows HTTP requests forwarded from the gateway. |
github_token_forbidden | GitHub refused the token for this request | A missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). | Grant the token access, then retry. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token (expired or revoked). | Link the account again with a new token and activate:true. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: repository 'octocat/Hello-World' not found", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git or gh command ran and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Sync (fetch → pull → push)
Section titled “Sync (fetch → pull → push)”Runs fetch → pull → push as one logical sync, stopping at the first non-ok step. direction lets a caller pull-only (fetch + pull) or push-only. set_upstream publishes a new branch and makes it track the same-named branch on its remote. force force-pushes the push leg (git push --force-with-lease), overwriting the remote branch’s history with no confirmation round.
POST /api/v1/agent/github/sync
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/sync \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"direction":"pull","set_upstream":false,"force":false}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.sync({ direction: 'pull', set_upstream: false, force: false }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "steps": [ { "step": "github.fetch", "result": { "status": "ok" } }, { "step": "github.pull", "result": { "status": "ok" } }, { "step": "github.push", "result": { "status": "ok" } } ]}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
direction | string | No | Optional: "pull" (fetch + pull) or "push" (push only). Default is the full fetch → pull → push. |
set_upstream | boolean | No | Optional, default false. PUBLISHES the current branch — push it and make it track the same-named branch on its remote. Use it for a branch that tracks nothing yet. Refused (502 sync_step_failed, step code upstream_exists) when the branch already tracks a DIFFERENT remote branch; a no-op when it already tracks exactly that one. Without it a push never changes tracking. |
force | boolean | No | Optional, default false. FORCE-PUSHES the push leg (git push --force-with-lease): it OVERWRITES THE REMOTE BRANCH’S HISTORY, discarding any remote commits your branch does not contain. The lease only refuses when the remote moved since this repository last fetched it, and the full sync fetches first, so on a full sync it protects almost nothing. There is no confirmation round: sending true is the intent. Ignored by the fetch and pull legs. Must be a JSON boolean. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "steps": [ { "step": "github.fetch", "result": { "status": "ok" } }, { "step": "github.pull", "result": { "status": "ok" } }, { "step": "github.push", "result": { "status": "ok" } } ]}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The request was refused by the server’s admin gate. | Run the daemon with socket auth (--auth-token-path) and no separate admin capability so its gate allows HTTP requests forwarded from the gateway. |
github_token_forbidden | GitHub refused the token for this request | A missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). | Grant the token access, then retry. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token (expired or revoked). | Link the account again with a new token and activate:true. |
github_account_missing | GitHub account no longer linked | The operation was pinned to one account and that account was removed while it ran. | Link an account, then retry. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "sync_step_failed", "message": "sync step failed", "details": { "failed_step": "github.push", "steps": [ { "step": "github.fetch", "result": { "status": "ok" } }, { "step": "github.pull", "result": { "status": "ok" } }, { "step": "github.push", "result": { "status": "error", "message": "updates were rejected (non-fast-forward)" } } ] }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
sync_step_failed | Sync step failed | A downstream git/gh step (fetch, pull, push) failed after the sync was accepted; the steps that ran plus the failing one are returned under details. | Resolve the underlying git state (conflict, transport, auth) and retry the sync. |
git_command_failed | Git command failed | A git/gh command ran in the repository and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Re-write a checkout’s GitHub credential
Section titled “Re-write a checkout’s GitHub credential”Writes the ACTIVE account’s token into a git credential file inside the requesting cwd’s checkout (<repo>/.git/hoody-credentials, readable only by its owner) and points the repository’s host-scoped credential.helper at it, so plain git push/pull in that checkout — including the agent’s own shell — authenticate as that account. Use it after re-linking or switching accounts, when a checkout still holds a dead or missing credential. The token is ON DISK afterwards, readable by anything that runs as the exec user. A subdirectory resolves to its repository root; a linked worktree or a separated git directory is refused (400), as is a path on the deny list or not inside a git repository. A checkout whose credential belongs to another linked account is re-pointed to the active one — that is the purpose of the call. There is no confirmation round, and a body persist_credentials is ignored (the route always persists). A failure to reach the checkout answers 503. Takes NO body fields; the repository comes from the X-Hoody-* scope headers.
POST /api/v1/agent/github/repo/reconnect
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/repo/reconnect \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.setRepoCredentials({}, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "repo_dir": "/workspace/my-repo", "account": "github.com/octocat"}The reconnect outcome: repo_dir is the repository root the credential was written into, and account is <host>/<login>.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”The schema declares no fields; the checkout is the one the X-Hoody-* scope headers select. Send an empty object.
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "repo_dir": "/workspace/my-repo", "account": "github.com/octocat"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters, including a path that is a linked worktree, a separated git directory, on the deny list, or not inside a git repository. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request, including a failure to reach the checkout. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Commits
Section titled “Commits”Stage all and commit
Section titled “Stage all and commit”Stages all changes and commits in the requesting cwd’s repo. Refuses the commit if the repository has unmerged index entries (unresolved merge conflicts).
POST /api/v1/agent/github/commit
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/commit \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"message":"Add login form"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.createCommit({ message: 'Add login form' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "[main 3a4b5c6] Add login form\n 1 file changed, 1 insertion(+), 1 deletion(-)"}Verbatim github.commit reply.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The commit message. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "[main 3a4b5c6] Add login form\n 1 file changed, 1 insertion(+), 1 deletion(-)"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The request was refused by the server’s admin gate. | Run the daemon with socket auth (--auth-token-path) and no separate admin capability so its gate allows HTTP requests forwarded from the gateway. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "unresolved_conflicts", "message": "this repository has unresolved merge conflicts — resolve them, then commit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
unresolved_conflicts | Unresolved merge conflicts | The repository has unmerged index entries. The commit is refused before staging. | Resolve the conflicts in the working tree, then commit again. |
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
nothing_to_commit | Nothing to commit | The working tree has no pending changes (tracked edits or new untracked files), so no commit was made. The repository is unchanged. | Make changes in the repository first. This is not a failure to retry: committing again without new changes answers the same. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 1, "output": "nothing to commit, working tree clean", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Stage all, commit and push
Section titled “Stage all, commit and push”Stages every change in the requesting cwd’s repository, commits it with message, and pushes the current branch to its upstream — one call instead of github.createCommit then github.sync(direction:"push"). NOTHING-TO-COMMIT IS NOT AN ERROR here: when the tree is clean it still pushes, so an earlier commit that was committed but never published goes out; the reply then says committed:false. PARTIAL FAILURE IS REPORTED HONESTLY: a push that fails after the commit landed answers 502 push_failed with details.committed:true — the work is committed locally, not published; do not commit it again. A push that TIMES OUT answers 503 service_unavailable with the same details.committed plus details.timed_out:true — the push may or may not have reached the remote, so check the remote before pushing again. A refusal because of the REPOSITORY’s state answers 409 with its own code — unsafe_transport_config (an http.* setting that could redirect the credential), unsafe_push_config, detached_head, invalid_branch, invalid_remote, no_remote, ambiguous_remote, unsafe_push_destination, upstream_exists (set_upstream against a branch that already tracks a different remote branch) — and when it comes after the commit landed, details.committed:true for the same reason. github.sync reports the same codes under its failing push step. A stage or commit step that git itself rejects (a failing commit hook, a path git refuses to add) answers 502 git_command_failed with details.committed:false and git’s output in details.output, exactly as github.createCommit does. With force:true the push runs git push --force-with-lease and OVERWRITES the remote branch’s history with the local one, discarding remote commits the local branch does not have (the lease only refuses when the remote moved since the last fetch); there is no confirmation round. The stage and commit steps run without the token; only the push carries it. Unmerged index entries are refused BEFORE staging with 409 unresolved_conflicts. Body confirmed/flat/persist_credentials/account_key and scope keys are stripped; the repository comes from the X-Hoody-* headers.
POST /api/v1/agent/github/commit/push
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/commit/push \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"message":"Add login form","set_upstream":false,"force":false}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.createCommit({ message: 'Add login form', set_upstream: false, force: false }, { XHoodyCwd: '/workspace/my-repo', push: true });{ "status": "ok", "success": true, "committed": true}The commit+push outcome. committed:false means the tree was clean and only already-committed work was pushed.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The commit message (required, non-blank). |
set_upstream | boolean | No | When true, publish the current branch — push it and make it track the same-named branch on its remote. Refused with 409 upstream_exists (with details.committed set when a commit landed) when the branch already tracks a DIFFERENT remote branch. Defaults to false. |
force | boolean | No | When true, force-pushes with git push --force-with-lease, OVERWRITING the remote branch’s history: remote commits the local branch lacks are discarded. No confirmation round. Defaults to false. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "committed": true}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters (including a blank message). | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "github_token_forbidden", "message": "GitHub refused this request for the linked token (github.com). The token lacks access to this repository or action: grant it the needed permission or repository access (fine-grained token), the needed scope (classic token), or authorize it for the organization (SAML SSO). Linking the same token again will not help."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_token_forbidden | GitHub refused the token for this request | A missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). | Grant the token access, then retry. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "unresolved_conflicts", "message": "this repository has unresolved merge conflicts — resolve them, then commit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
unresolved_conflicts | Unresolved merge conflicts | The repository has unmerged index entries. The commit is refused BEFORE staging: git add -A would mark those paths resolved while their conflict markers remained, committing — and through commit_push, publishing — the markers. The conflicting paths are returned under details.paths. | Resolve the conflicts in the working tree, then commit again. |
unsafe_transport_config | Unsafe transport configuration | The repository’s own .git/config sets an http.* option that could redirect or intercept the GitHub credential, or its http.* configuration is too large to vet. Nothing that carries the token ran. When this call’s commit DID land before the refusal, details.committed is true. | Remove the named http.* setting from the repository’s .git/config, then retry. |
unsafe_push_config | Unsafe push configuration | The repository’s configuration would redirect the push or turn it into a force-update, a deletion or a mirror. The push did not run. When this call’s commit DID land before the refusal, details.committed is true. | Review the repository’s .git/config, remove the setting the message names, then retry. |
detached_head | Detached HEAD | HEAD is not on a branch. The push did not run. When this call’s commit DID land before the refusal, details.committed is true. | Create or switch to a branch (github.createBranch / github.useBranch), then push. |
invalid_branch | Unsafe branch name | The current branch’s name is not one git round-trips safely as a refspec. The push did not run. When this call’s commit DID land before the refusal, details.committed is true. | Rename the branch to an ordinary ref name, then push. |
invalid_remote | Unsafe remote name | The remote the push would go to has a name that is not safe to pass to git. The push did not run. When this call’s commit DID land before the refusal, details.committed is true. | Rename the remote to an ordinary name, then push. |
no_remote | No remote | The repository has no remote to push to, or its remotes could not be listed. The push did not run. When this call’s commit DID land before the refusal, details.committed is true. | Add a remote to the repository, then push. |
ambiguous_remote | Ambiguous remote | The branch tracks no remote and the repository has several, so the push is refused rather than guessing where it goes. The message names the candidates. The push did not run. When this call’s commit DID land before the refusal, details.committed is true. | Set an upstream for the branch (or push with set_upstream on a repository with one remote), then push. |
unsafe_push_destination | Unsafe push destination | The remote’s resolved push destination is unsafe: it has no push URL or several, it pushes somewhere other than it fetches from, or it does not point at the linked account’s host over https or ssh. The push did not run. When this call’s commit DID land before the refusal, details.committed is true. | Review the remote’s URLs in the repository’s .git/config, then retry. |
upstream_exists | Branch already tracks another branch | set_upstream was requested, but the branch already tracks a DIFFERENT remote branch; publishing would silently re-point that tracking. The push did not run. When this call’s commit DID land before the refusal, details.committed is true. | Push without set_upstream, or change the branch’s tracking yourself. |
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token itself. | Link the account again with a new token, sending activate:true so it replaces the dead credential, then retry. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "committed": false, "exit_code": 1, "output": "pre-commit hook failed: lint errors", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | A git command of the stage/commit leg ran in the repository and exited non-zero (the commit itself, git add, a pre-commit check), or a push-destination check did. Nothing was published; details.committed is false when the commit leg failed. details.output carries git’s own token-redacted output and details.exit_code its exit status. A failed PUSH answers 502 push_failed instead. A failure CLASSIFIED as an account problem answers with its github_* code. | Read details.output — it names the git-level cause. Resolve it in the repository and retry. |
push_failed | Push failed | The push leg of a commit+push ran and was refused (a rejected non-fast-forward, a protected branch, a transport error). details.committed is true when the commit DID land locally in this call — the work is committed but not published; it is absent when there was nothing new to commit and only the push of an earlier commit was attempted. details.output carries git’s token-redacted output. | Read details.output. Do NOT re-commit: when details.committed is true, fix the cause (pull or sync first for a non-fast-forward) and push again with github.sync or another github.createCommit. |
{ "code": "service_unavailable", "message": "committed locally, but the push timed out; whether it reached the remote is unknown — check the remote before pushing again, and do not commit again", "details": { "committed": true, "timed_out": true, "unavailable": true }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | A step could not complete: the repository could not be reached to run a check or the commit, or the push timed out — in which case it may or may not have reached the remote (details.timed_out). The daemon may also be too busy. details.committed is true when this call’s commit DID land first: do not commit it again. | Retry. When details.committed is true, check the remote (github.getStatus) before pushing again, and do not re-commit. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory (permissions, disk), then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Draft a commit message with a model
Section titled “Draft a commit message with a model”Drafts a commit message for the requesting cwd’s pending changes with one tool-free call to model. It does NOT commit and does NOT touch the index: the diff is built in a throwaway index seeded from HEAD, covering tracked edits AND new untracked files (what github.createCommit would record), and is capped before it reaches the model. The diff is SENT TO THE MODEL’S PROVIDER — do not call it on a repository whose contents must not leave the box. The call is billed; the reply carries its cost_usd. model must be a concrete model spec (provider/model); a fusion / composite is refused. A clean tree answers 409 nothing_to_commit and makes no model call. Paths on the deny list are refused. Runs without the GitHub token. Scope keys in the body are stripped; the repository comes from the X-Hoody-* headers.
POST /api/v1/agent/github/commit/suggest-message
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/commit/suggest-message \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"model":"anthropic/claude-sonnet-4-6"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.suggestCommitMessage({ model: 'anthropic/claude-sonnet-4-6' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "message": "Add login form\n\nImplements OAuth login with PKCE.", "cost_usd": 0.0021}The drafted commit message: a subject line, optionally a blank line and a short body. Nothing was committed.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | A concrete model spec, e.g. "anthropic/claude-sonnet-4-6". A fusion / composite cannot back a one-shot call and is refused. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "message": "Add login form\n\nImplements OAuth login with PKCE.", "cost_usd": 0.0021}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters (including a missing or invalid model, or a path on the deny list). | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "nothing_to_commit", "message": "nothing to commit — working tree clean"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
nothing_to_commit | Nothing to commit | The working tree has no pending changes (tracked edits or new untracked files), so there is no diff to draft a commit message from. No model call was made and nothing was billed. | Make changes in the repository first. |
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Issues and pull requests
Section titled “Issues and pull requests”List issues
Section titled “List issues”Lists a repository’s issues through the GitHub REST API. Pull requests are filtered out. Sorted by most-recently-updated, 4 pages of 50, at most 200 issues. owner and repo are required query parameters; resolve them with GET /github/identity.
GET /api/v1/agent/github/issues
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/issues?owner=octocat&repo=Hello-World&state=open" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "X-Hoody-Cwd: /workspace/my-repo"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.listIssues({ owner: 'octocat', repo: 'Hello-World', state: 'open', XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "issues": [ { "number": 42, "title": "Add login form", "state": "open", "author": "octocat", "url": "https://github.com/octocat/Hello-World/issues/42" } ]}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
owner | query | string | Yes | Repository owner (user or org login). Required, not derived from the bound checkout. |
repo | query | string | Yes | Repository name without the owner. Required, same source as owner. |
state | query | string | No | Which issues to return: open (default), closed, or all. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "issues": [ { "number": 42, "title": "Add login form", "state": "open", "author": "octocat", "url": "https://github.com/octocat/Hello-World/issues/42" } ]}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters (missing owner or repo). | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "github_token_forbidden", "message": "GitHub refused this request for the linked token (github.com). The token lacks access to this repository or action: grant it the needed permission or repository access (fine-grained token), the needed scope (classic token), or authorize it for the organization (SAML SSO). Linking the same token again will not help."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_token_forbidden | GitHub refused the token for this request | GitHub refused this request for the linked token: a missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). Retrying or linking the same token again does not help. details.account_failure carries {kind, source, host, login, remedy, message}; details.recovery says what to do. | Grant the token access (fine-grained: repository + permission; classic: scope; organization: SSO authorization), then retry. See details.recovery.checklist and details.permission_hints. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
github_not_found | Not found on GitHub | GitHub answered 404. For a private resource that is ALSO what a token without access receives, so this can mean the linked token cannot see it; it is deliberately not reported as a permission failure. details.github_message carries GitHub’s text. | Check the owner/name. If it exists, check that the linked token can see it. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry.", "details": { "account_failure": { "host": "github.com", "kind": "not_linked", "login": "", "message": "No GitHub account is linked for this realm. Link an account, then retry.", "remedy": "link_account", "source": "store" } }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm, so the operation has no identity to run as. details.account_failure carries {kind, source, host, login, remedy, message}; details.recovery says what to do. | Link an account (client.agent.github.login() with activate:true, or client.agent.github.pollLogin() with activate:true for the device flow), then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. Linking an account resets it. details.account_failure carries {kind, source, host, login, remedy, message}; details.recovery says what to do. | Link an account again; that resets the unreadable store. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token itself — expired or revoked. details.account_failure carries {kind, source, host, login, remedy, message}; details.recovery says what to do. | Link the account again with a new token, sending activate:true so it replaces the dead credential, then retry. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written (I/O or permissions). Nothing is known about the accounts in it. details.account_failure carries {kind, source, host, login, remedy, message}; details.recovery says what to do. | Check the daemon’s config directory (permissions, disk), then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Open an issue
Section titled “Open an issue”Opens an issue on the repository the requesting cwd’s checkout belongs to. The target comes from the cwd scope, not the body. title is required and non-empty.
POST /api/v1/agent/github/issues
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/issues \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"title":"Add login form","body":"Implements OAuth login."}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.createIssue({ title: 'Add login form', body: 'Implements OAuth login.' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "https://github.com/octocat/Hello-World/issues/43"}Verbatim github.issue.create exec reply. The new issue’s URL appears in output.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | The issue title (required, non-empty). |
body | string | No | The issue body. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "https://github.com/octocat/Hello-World/issues/43"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters (including an empty title). | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "github_token_forbidden", "message": "GitHub refused this request for the linked token (github.com). The token lacks access to this repository or action: grant it the needed permission or repository access (fine-grained token), the needed scope (classic token), or authorize it for the organization (SAML SSO). Linking the same token again will not help."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_token_forbidden | GitHub refused the token for this request | A missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). | Grant the token access, then retry. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token (expired or revoked). | Link the account again with a new token and activate:true. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 1, "output": "gh: Issues have been disabled on this repository", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | gh ran and failed (no linked account with issue permission, issues disabled on the repository, an unresolvable remote). | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
List pull requests
Section titled “List pull requests”Lists a repository’s pull requests through the GitHub REST API. Sorted by most-recently-updated, at most 200 PRs across 4 pages of 50. owner and repo are required; resolve them with GET /github/identity.
GET /api/v1/agent/github/pr
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/pr?owner=octocat&repo=Hello-World&state=open" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "X-Hoody-Cwd: /workspace/my-repo"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.listPrs({ owner: 'octocat', repo: 'Hello-World', state: 'open', XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "pulls": [ { "number": 42, "title": "Add login form", "state": "open", "draft": false, "merged": false, "author": "octocat", "head": "feature", "url": "https://github.com/octocat/Hello-World/pull/42" } ]}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
owner | query | string | Yes | Repository owner (user or org login), e.g. "octocat". Required, not derived from the bound checkout. |
repo | query | string | Yes | Repository name without the owner, e.g. "Hello-World". Required, same source as owner. |
state | query | string | No | Which pull requests to return: open (default), closed, or all. An unrecognized value is passed through to GitHub, which rejects it. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "pulls": [ { "number": 42, "title": "Add login form", "state": "open", "draft": false, "merged": false, "author": "octocat", "head": "feature", "url": "https://github.com/octocat/Hello-World/pull/42" } ]}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters (missing owner or repo). | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "github_token_forbidden", "message": "GitHub refused this request for the linked token (github.com). The token lacks access to this repository or action: grant it the needed permission or repository access (fine-grained token), the needed scope (classic token), or authorize it for the organization (SAML SSO). Linking the same token again will not help."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_token_forbidden | GitHub refused the token for this request | GitHub refused this request for the linked token: a missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). Retrying or linking the same token again does not help. details.account_failure carries {kind, source, host, login, remedy, message}; details.recovery says what to do. | Grant the token access (fine-grained: repository + permission; classic: scope; organization: SSO authorization), then retry. See details.recovery.checklist and details.permission_hints. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
github_not_found | Not found on GitHub | GitHub answered 404. For a private resource that is ALSO what a token without access receives, so this can mean the linked token cannot see it; it is deliberately not reported as a permission failure. details.github_message carries GitHub’s text. | Check the owner/name. If it exists, check that the linked token can see it. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry.", "details": { "account_failure": { "host": "github.com", "kind": "not_linked", "login": "", "message": "No GitHub account is linked for this realm. Link an account, then retry.", "remedy": "link_account", "source": "store" } }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm, so the operation has no identity to run as. details.account_failure carries {kind, source, host, login, remedy, message}; details.recovery says what to do. | Link an account (client.agent.github.login() with activate:true, or client.agent.github.pollLogin() with activate:true for the device flow), then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. Linking an account resets it. details.account_failure carries {kind, source, host, login, remedy, message}; details.recovery says what to do. | Link an account again; that resets the unreadable store. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token itself — expired or revoked. details.account_failure carries {kind, source, host, login, remedy, message}; details.recovery says what to do. | Link the account again with a new token, sending activate:true so it replaces the dead credential, then retry. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written (I/O or permissions). Nothing is known about the accounts in it. details.account_failure carries {kind, source, host, login, remedy, message}; details.recovery says what to do. | Check the daemon’s config directory (permissions, disk), then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Open a pull request
Section titled “Open a pull request”Opens a pull request for the requesting cwd’s repo. title is required and non-empty. body is optional. base overrides the base branch (default the repo default).
POST /api/v1/agent/github/pr
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/pr \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"title":"Add login form","body":"Implements OAuth login.","base":"main"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.createPr({ title: 'Add login form', body: 'Implements OAuth login.', base: 'main' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "https://github.com/octocat/Hello-World/pull/43\n"}The PR is opened; the PR link is inside output. This reply carries no parsed url or number fields; read output to find them.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | The PR title (required, non-empty). |
body | string | No | The PR description. |
base | string | No | Optional base branch (default the repo default). |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "https://github.com/octocat/Hello-World/pull/43\n"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters (including an empty title). | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The request was refused by the server’s admin gate. | Run the daemon with socket auth (--auth-token-path) and no separate admin capability so its gate allows HTTP requests forwarded from the gateway. |
github_token_forbidden | GitHub refused the token for this request | A missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). | Grant the token access, then retry. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token (expired or revoked). | Link the account again with a new token and activate:true. |
unsafe_transport_config | Unsafe transport configuration | The repository’s own .git/config sets an http.* option that could redirect or intercept the GitHub credential (a forced resolve, TLS verification turned off, a proxy), or its http.* configuration is too large to vet. Nothing that carries the token ran. | Remove the named http.* setting from the repository’s .git/config, then retry. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 1, "output": "gh: pull request create failed: a pull request already exists", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | gh ran and failed. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Check out a pull request
Section titled “Check out a pull request”Checks out a pull request’s head commit in the requesting cwd’s repository. IT LEAVES THE REPOSITORY IN DETACHED HEAD, deliberately: the daemon fetches the PR head into a transient ref and checks that ref out detached rather than creating or force-updating a local branch, so a pre-existing branch of the same name is never overwritten and its commits are never orphaned. Commit from here and you commit onto no branch — create one with github.createBranch first if you intend to keep the work. NOTE the consequence for pushing: github.sync refuses a detached HEAD, because there is no branch to push. The fetch carries the token and the worktree update does NOT — they are separate processes precisely so a hostile in-tree filter cannot run with the credential in its environment. GitHub (and GHES) expose every PR, cross-fork included, under the base repository’s refs, so no fork remote is needed. number is a positive PR number, as a decimal string ("42") or a JSON integer (what github.listPrs returns); 0, a fraction, a negative or a non-numeric value is refused. A PR that does not exist, or a checkout blocked by local changes, comes back as 502 git_command_failed with git’s output. A repository whose .git/config sets an http.* option that could redirect the credential is refused before the fetch with 409 unsafe_transport_config.
POST /api/v1/agent/github/pr/checkout
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/pr/checkout \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"number":42}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.checkoutPr({ number: 42 }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Note: switching to 'pull/42/head'.\nYou are in 'detached HEAD' state..."}Verbatim github.pr.checkout exec reply. The repository is now in detached HEAD.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
number | string | integer | Yes | The PR number (required): a positive decimal string such as "42", or the JSON integer github.listPrs returns. An empty value, 0, a fraction, a negative or anything non-numeric is refused, so a branch name or URL can never be smuggled in as a target. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Note: switching to 'pull/42/head'.\nYou are in 'detached HEAD' state..."}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters (including an empty, 0, fractional, negative or non-numeric number). | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "github_token_forbidden", "message": "GitHub refused this request for the linked token (github.com). The token lacks access to this repository or action: grant it the needed permission or repository access (fine-grained token), the needed scope (classic token), or authorize it for the organization (SAML SSO). Linking the same token again will not help."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_token_forbidden | GitHub refused the token for this request | A missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). | Grant the token access, then retry. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "unsafe_transport_config", "message": "refusing authenticated git: this repository's .git/config sets http.https://github.com/.sslverify, which can redirect the GitHub credential to another server — remove it and retry"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
unsafe_transport_config | Unsafe transport configuration | The repository’s own .git/config sets an http.* option that could redirect or intercept the GitHub credential, or its http.* configuration is too large to vet. Nothing that carries the token ran. | Remove the named http.* setting from the repository’s .git/config, then retry. |
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token itself. | Link the account again with a new token, sending activate:true so it replaces the dead credential, then retry. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 128, "output": "fatal: not a git repository", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | The git command ran and exited non-zero, including a PR that does not exist or a checkout blocked by local changes. | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Merge a pull request
Section titled “Merge a pull request”Merges a pull request ON GITHUB with the active account’s token (gh pr merge), in the repository the requesting cwd’s checkout belongs to. It changes the BASE BRANCH ON THE REMOTE and cannot be undone by this API. DESTRUCTIVE, with NO confirmation round: calling this route is the intent (a body confirmed is ignored; the gateway confirms server-side). method picks merge (default), squash or rebase; any other value is refused with 400 rather than falling back to a merge commit. The head branch is not deleted and the local checkout is not updated — run github.sync afterwards to pull the result. A PR that cannot be merged (conflicts, failing required checks, missing permission) comes back as 502 git_command_failed with gh’s output, or as the account’s github_* code when the token itself is refused.
POST /api/v1/agent/github/pr/merge
curl -X POST https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/github/pr/merge \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Hoody-Cwd: /workspace/my-repo" \ -d '{"number":42,"method":"merge"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.github.mergePr({ number: 42, method: 'merge' }, { XHoodyCwd: '/workspace/my-repo' });{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Merged pull request #42 (Add login form)."}Verbatim github.pr.merge exec reply.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container. |
X-Hoody-Realm | header | string | No | Per-request realm selector. Rejected with 400 realm_scope_unsupported. |
realm | query | string | No | Query alias of X-Hoody-Realm. Rejected with 400 realm_scope_unsupported. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
number | string | integer | Yes | The PR number (required): a positive decimal string such as "42", or the JSON integer github.listPrs returns. Anything else (0, a fraction, a branch name or URL) is refused, so the merge can never target a different PR. |
method | string | No | Optional merge method, default merge (a merge commit). squash squashes the PR into one commit; rebase rebases its commits onto the base. Allowed values: merge, squash, rebase. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "success": true, "exit_code": 0, "timed_out": false, "output": "Merged pull request #42 (Add login form)."}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters (including an empty, 0, fractional, negative or non-numeric number, or a method outside merge, squash, rebase). | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied. | Omit the realm header on this route. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "github_token_forbidden", "message": "GitHub refused this request for the linked token (github.com). The token lacks access to this repository or action: grant it the needed permission or repository access (fine-grained token), the needed scope (classic token), or authorize it for the organization (SAML SSO). Linking the same token again will not help."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_token_forbidden | GitHub refused the token for this request | A missing fine-grained permission or classic scope, a repository outside the token’s grant, or an organization that has not authorized it (SAML SSO). | Grant the token access, then retry. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "github_not_linked", "message": "No GitHub account is linked for this realm. Link an account, then retry."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
github_not_linked | No GitHub account linked | No account is linked in this gateway’s realm. | Link an account, then retry. |
github_store_corrupt | GitHub credential store corrupt | The credential store file cannot be parsed. | Link an account again to reset it. |
github_token_invalid | GitHub token invalid | GitHub rejected the linked token (expired or revoked). | Link the account again with a new token and activate:true. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap. | Reduce the body below the limit; split large payloads into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client rate limit was exceeded before dispatch. | Honor Retry-After and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |
{ "code": "git_command_failed", "message": "the git command ran and failed — see details.output", "details": { "exit_code": 1, "output": "gh: Pull Request #42 is not mergeable: merge conflict", "timed_out": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
git_command_failed | Git command failed | gh ran and exited non-zero (conflicts, failing required checks, missing permission). | Read details.output for the cause. Resolve it and retry. If details.account_observation is present, also check the linked account. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request. | Honor Retry-After and retry. |
github_store_unavailable | GitHub credential store unavailable | The credential store could not be read or written. | Check the daemon’s config directory, then retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |