> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: Orby pending-action approval

> Decode the Orby approval-window codes — PENDING_ACTION_NOT_APPROVABLE, PENDING_ACTION_RACE, PENDING_ACTION_EXPIRED, and PENDING_ACTION_PERSIST_FAILED — find the action again, avoid the two-operator race, and retry the right way.

# Troubleshooting: Orby pending-action approval

When a turn proposes an action that changes data — send a message, reassign
a conversation, pause a campaign — Orby does not run it on the spot. It
registers a pending action and renders an **approval card** in the chat. The
action executes only when an operator explicitly approves it, and the window
closes five minutes after the proposal. Every failure in that window carries
a stable `PENDING_ACTION_*` code; this page maps each one to its cause and
its recovery. The full platform code list lives in the
[error reference](/reference/error-codes); this page is the runbook for the
approval path. Session-token and authorization failures on the same panel are
on the sibling page, [Orby operator errors](/troubleshooting/orby-operator-errors).

## 1. Why an approval window exists

Every data-changing proposal lands in a `pending_agent_actions` row and waits
there until an operator explicitly approves or rejects it. The row records
the tool, the exact arguments, and the proposing operator, so what you
approve is a concrete, auditable payload — not "whatever the model meant."
Approval is a deliberate gate, not a formality: it keeps the operator in
control of every write Orby performs. Approvals and rejections are
audit-logged, including who acted and on which payload. The five-minute
window exists so a stale proposal cannot be approved after its context has
moved.

## 2. Code table

| Code                            | HTTP | Cause                                                                                                                                                                                                                                    | Fix                                                                                                                                                                                                   |
| ------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PENDING_ACTION_NOT_APPROVABLE` | 409  | The row already left the `pending` state because someone approved, rejected, or cancelled it. The same code also fires when a retried approve hits an already-completed row — the second attempt refuses so the action never runs twice. | Ask Orby to re-issue the proposal. If a teammate may have acted first, check the action's current state instead of re-approving blind — `GET /api/v1/orby/tool-actions/:id` returns the row's status. |
| `PENDING_ACTION_RACE`           | 409  | Two operators pressed approve or reject at the same time and yours was the loser.                                                                                                                                                        | Re-ask Orby for the proposal if you still want it done. Keep one operator as the approver to eliminate the race.                                                                                      |
| `PENDING_ACTION_EXPIRED`        | 409  | The five-minute approval window elapsed before anyone acted. The expired row is cancelled so a later approve cannot slip a stale proposal through.                                                                                       | Ask Orby to propose the action again — a new card starts a new five-minute window. Mute the countdown only when you know you will approve.                                                            |
| `PENDING_ACTION_PERSIST_FAILED` | 500  | The runtime could not record the pending action at proposal time, so the tool was refused outright. This is fail-closed: a proposal that cannot be recorded never executes.                                                              | Retry the turn once. If it recurs, collect the `request_id` and escalate — the proposal write failed, not your request.                                                                               |

A sample envelope for a lapsed window — key on `error.code`, never on the
`message` text:

```json theme={null}
{
  "error": {
    "code": "PENDING_ACTION_EXPIRED",
    "message": "Approval window elapsed before the operator acted.",
    "status": 409
  },
  "meta": {
    "request_id": "req_orby_0a1",
    "timestamp": "2026-09-16T09:41:12.388Z"
  }
}
```

## 3. Operator flow

1. Open the Orby chat where the proposal was made. The pending action card
   shows the tool, its exact arguments, and the id; a custom client reads
   the `pending_action_id` off the `tool_pending` SSE event.
2. To re-check whether the card is still live before approving, read the
   action — `GET /api/v1/orby/tool-actions/:id` returns the row's status and
   its `expires_at`, so you can see the remaining window without guessing.
3. If the card is gone or the read says the action is `done`, `rejected`, or
   `cancelled`, do not re-submit the same approve call — ask Orby to
   re-issue the proposal and approve the fresh card. An approve on a
   non-pending row is exactly what returns
   `PENDING_ACTION_NOT_APPROVABLE`.
4. Assign approval to one operator. Pending actions belong to the proposing
   operator, so a teammate re-issuing the proposal is the clean way to hand
   a proposal over — not two people racing the same card.

## 4. Retry safety

| Code                            | Retry class                                                      |
| ------------------------------- | ---------------------------------------------------------------- |
| `PENDING_ACTION_NOT_APPROVABLE` | Never retry the same approve call — re-ask for a fresh proposal. |
| `PENDING_ACTION_RACE`           | Not retriable as-is; re-issue the proposal if still wanted.      |
| `PENDING_ACTION_EXPIRED`        | Not retriable; re-issue the proposal.                            |
| `PENDING_ACTION_PERSIST_FAILED` | Retry the turn once, then escalate with the `request_id`.        |

None of the 409s is retriable — they re-fail deterministically on repeat. The
recovery is always "new proposal, approve promptly." Only
`PENDING_ACTION_PERSIST_FAILED` is a transient 500 worth a single retry.

## When to escalate

Escalate to support when:

* `PENDING_ACTION_PERSIST_FAILED` recurs after a single retry — the proposal
  write is failing, not your request.
* A 409 code appears on a proposal you are certain nobody else touched —
  that contradicts the single-approver flow and is worth a trace.
* The approval card disappears before the five-minute window visibly
  elapses.

Include the `request_id` from the `meta` block (or the `error` SSE event),
the pending-action id when you have it, and the tool name. The request id
threads through the turn, dispatch, and approval paths, so support can pull
the executor-side trace without a back-and-forth.

## See also

* [Orby operator errors](/troubleshooting/orby-operator-errors) — the
  session-token and tool-authorization failures on the same panel.
* [Orby Assistant API](/api-reference/orby) — the `tool_pending` SSE event,
  the approve/reject endpoints, and the action-state read.
* [Using Orby in the dashboard](/guides/orby-in-dashboard) — the operator
  workflow on top of these endpoints.
* [Error reference](/reference/error-codes) — the full code catalog.
