--- title: 'hitl' description: 'List, read and decide the human-in-the-loop approvals parked on a running agent installation.' position: 16 --- # stackbone hitl > Every verb targets one installation: the local-dev install by default, any > install with [`--agent `](/docs/cli/reference/conventions#target-resolution). > `list` is [cursor-paginated](/docs/cli/reference/conventions#pagination). > `approve` and `reject` are > [destructive](/docs/cli/reference/conventions#destructive-verbs) and require `--yes`. Human-in-the-loop approvals for the targeted installation. This inbox is the **return path** for a parked durable run: when a workflow step calls `requestApproval()` (from `@stackbone/sdk/workflow`, see [Workflows → human-in-the-loop](/docs/sdk/workflows/overview)), the run pauses and the pending approval surfaces here. The same inbox carries `defineHook` pauses and any tool call an agent parked for a human decision. Deciding an approval (`approve` or `reject`) resumes the parked run with your decision. An approval's `runId` field links back to the run you can follow with [`stackbone runs get`](/docs/cli/reference/runs#stackbone-runs-get). Editing an approval payload is a Studio-only action. The CLI sends only the decision and an optional comment. | Command | Purpose | | ----------------------------- | -------------------------------------------------------------------------------------------------------------- | | `stackbone hitl list` | List approvals. Filters: `--status `, `--limit`/`--cursor`. | | `stackbone hitl get ` | Inspect one approval (topic, run, timeout, and its audit trail of past decisions). | | `stackbone hitl approve ` | Approve a pending approval (`--reason`), resuming the parked run. Requires `--yes`. | | `stackbone hitl reject ` | Reject a pending approval (`--reason`), resuming the parked run. Requires `--yes`. | The runtime mints an approval id (a UUID) when it creates the pause. ## stackbone hitl list The human-in-the-loop **approvals inbox**: the return path for [`requestApproval()`](/docs/sdk/workflows/overview) (and `defineHook`) calls made from your workflows, plus any tool call an agent paused for a human decision. Each pending entry is a step waiting on a human decision. | Flag | Type | Description | | ---------- | ------ | ------------------------------------------------------------------------------ | | `--agent` | string | Installation id to target. Defaults to the local-dev install. | | `--status` | string | Filter by status: `pending`, `approved`, `rejected`, `timed_out`, `cancelled`. | | `--cursor` | string | Opaque page cursor (`nextCursor` / `prevCursor`). | | `--limit` | string | Maximum number of approvals to return (1-100). | Human mode prints one line per approval (id, status, topic, created-at), and `No approvals found.` when the page is empty. **JSON payload** ```jsonc { "schema_version": 1, "items": [ { "id": "3f6b0a1e-7c4d-4b21-9e88-2a5f1c0d9a44", "status": "pending", "topic": "refund", "createdAt": "2026-06-01T10:00:00Z", }, ], "nextCursor": "eyJ..." /* or null */, "prevCursor": null, } ``` ## stackbone hitl get Read one approval, including its audit trail of past decisions. | Flag | Type | Description | | --------- | ------ | ------------------------------------------------------------- | | `--agent` | string | Installation id to target. Defaults to the local-dev install. | Human mode prints the record as labelled lines: `id`, `status`, `topic`, `run`, `created`, `timeout`, `decided`, and a `decisions` count. Those labels are the human view; the JSON keys are the wire names below. **JSON payload** ```jsonc { "schema_version": 1, "approval": { "id": "3f6b0a1e-7c4d-4b21-9e88-2a5f1c0d9a44", "status": "pending", "topic": "refund", "runId": "0a9d2c17-5f83-4d0e-8f61-9c2b7e4a1d33" /* or null */, "createdAt": "2026-06-01T10:00:00Z", "timeoutAt": "2026-06-02T10:00:00Z" /* or null */, "decidedAt": null, "fallback": "reject" /* the expiry policy, or null */, "decisions": [], }, } ``` The record also carries `agentId`, `workspaceId`, `requestedByStepId`, `payload`, `schema`, `callbackUrl` and `metadata`. ## stackbone hitl approve Decide a pending approval, resuming the durable workflow or agent turn that is parked on it. Destructive: requires `--yes`. `--reason` attaches an optional comment, recorded on the decision. ```bash stackbone hitl approve 3f6b0a1e-7c4d-4b21-9e88-2a5f1c0d9a44 --yes stackbone hitl approve 3f6b0a1e-7c4d-4b21-9e88-2a5f1c0d9a44 --yes --reason "Verified with the customer" ``` | Flag | Type | Description | | ---------- | ------ | ------------------------------------------------------------- | | `--agent` | string | Installation id to target. Defaults to the local-dev install. | | `--reason` | string | Note recorded with the decision. | Human mode prints `Approved approval .` **JSON payload** ```jsonc { "schema_version": 1, "approval": { "id": "3f6b...", "status": "approved", "decidedAt": "2026-06-01T11:00:00Z" }, "decision": { "id": "b2c8...", "approvalId": "3f6b...", "decision": "approve", "comment": "Verified with the customer" /* or null */, "actorEmail": "you@example.com" /* or null */, "decidedAt": "2026-06-01T11:00:00Z", }, } ``` The `approval` object is the same record `hitl get` returns, minus the `decisions` trail. ## stackbone hitl reject The mirror of `approve`: aborts the parked run's approval with a `reject` decision. Destructive: requires `--yes`. Same flags, same JSON payload, with `decision` set to `reject`. ```bash stackbone hitl reject 3f6b0a1e-7c4d-4b21-9e88-2a5f1c0d9a44 --yes --reason "Out of policy" ``` Human mode prints `Rejected approval .` ## Timeouts The runtime's timeout sweep closes a pending approval as `timed_out` once its `timeoutAt` deadline passes with no decision. The sweep records the expiry policy the step declared as a system decision, then applies it to whatever the approval was blocking. The policy is `reject` unless the step said otherwise (`fallback` on a workflow's `requestApproval()`, `onTimeout` on `stackbone.approval.request()`). An approval created with `onTimeout: 'ignore'` carries no policy. Its `fallback` is null and the sweep skips it. It stays `pending` until a person decides it, whatever its `timeoutAt` says. **Exit codes**: `0` ok · `3` no target (no project and no `--agent`, or `stackbone dev` is not running) · `4` not found (unknown approval, or a targeted box with no registered deployment) · `5` permission (`approve`/`reject` without `--yes`) · `1` generic. Only a `pending` approval can be decided, so `approve` and `reject` also exit `1` on an approval somebody already decided, and on one the timeout sweep already closed. The full table is in [Shared conventions](/docs/cli/reference/conventions#exit-codes).