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

# Reference

> session_agent_harness error codes and limitations

This page is the reference for the `session_agent_harness` job type: the error codes it can raise — in three
classes beyond the [generic process codes](/worker/reference) that fire before any section runs — and the v1
limitations specific to driving agents over a session.

## Section validation

These fire during preflight, before the section is contacted. They exit the process with the code, the same
way the generic process codes do.

| Code                       | Cause                                                                                                                | Remediation                             |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| `MISSING_SESSION_KEYS`     | A section is missing one of `org_id`, `blob_id`, `revision_id`, `session_id`.                                        | Complete the section's `session` block. |
| `DUPLICATE_SECTION_TARGET` | Two sections target the same `(org_id, blob_id, revision_id, session_id, session_prefix)` tuple, checked statically. | Remove one of the duplicates.           |

This static check can't catch two sections naming the **same** session under different `session_id`
spellings (one by uuid, one by alias) — that collision surfaces after resolution instead, as the
section-level `DUPLICATE_SECTION_TARGET` below.

## Section-level errors

One section fails to attach to its session; the failure is persisted in
`jobs/{job_id}/section.yaml.attachment.error` and the section stops, while the worker keeps running all other
sections.

| Code                              | Cause                                                                                                                                                                                                                                                                                                                             | Remediation                                                                                                             |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `SESSION_NOT_FOUND`               | `session_id` doesn't resolve — a bad uuid, an alias with no live session behind it, the session 404'd, or the API user lacks access.                                                                                                                                                                                              | Check the `(org_id, blob_id, revision_id, session_id)` tuple, confirm an alias spelling is live, and check permissions. |
| `SESSION_OWNED_BY_DIFFERENT_USER` | The `worker` session object's `metadata.user.user_id` differs from ours.                                                                                                                                                                                                                                                          | Manually free the session: `delete_session_object(session_id, alias="worker")`.                                         |
| `SECTION_WORKER_OBJECT_MALFORMED` | The `worker` session object exists but its `value.type` is not `"thread"`.                                                                                                                                                                                                                                                        | Delete the malformed object and let the worker recreate it.                                                             |
| `SESSION_DETACHED_EXTERNALLY`     | The `worker` session object was deleted while the section was attached.                                                                                                                                                                                                                                                           | Re-attach by restarting the worker (or wait — the worker retries on next poll).                                         |
| `DUPLICATE_SECTION_TARGET`        | This section's `session_id` (uuid or alias) resolved, after attach, to a `(session, session_prefix)` pair another already-attached section had already claimed — the two config entries named the same session under different spellings, which the static config check (see [Configuration](/worker/configuration)) can't catch. | Point one of the two sections at a different session or prefix, or remove the duplicate.                                |

## Thread-level errors

A single thread transitions to `failed`, terminal until the user resets it (`instance.state = "pending"`).
The error detail is persisted **locally** in `jobs/.../threads/{alias}/thread.yaml` (`agent.error`) — it is
**not** written to the thread envelope. It surfaces to other clients as a `thread_failed` item in the worker
activity thread.

| Code                          | Cause                                                                                                                                                                                                                                      | Remediation                                                                                                                |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `WORK_FOLDER_NOT_ABSOLUTE`    | `workspace.work_folder` is not an absolute path.                                                                                                                                                                                           | Update the envelope to use an absolute path.                                                                               |
| `WORK_FOLDER_NOT_FOUND`       | The path doesn't exist on the worker host.                                                                                                                                                                                                 | Create the directory or fix the path.                                                                                      |
| `WORK_FOLDER_NOT_A_DIR`       | The path exists but isn't a directory.                                                                                                                                                                                                     | Point at an actual directory.                                                                                              |
| `WORK_FOLDER_NOT_READABLE`    | The path exists but the worker process can't access it.                                                                                                                                                                                    | `chmod` / `chown` the directory so the worker user can read+execute it.                                                    |
| `AGENT_TYPE_UNSUPPORTED`      | `agent.type` is not in `claude_code` / `codex`.                                                                                                                                                                                            | Use a supported value.                                                                                                     |
| `PERMISSIONS_UNSUPPORTED`     | `agent.permissions` is not in `autonomous` / `approval` / `guarded`.                                                                                                                                                                       | Use a supported value (default is `approval`).                                                                             |
| `POLICY_INVALID`              | The thread's `policy` block is malformed — a wrong-shaped value (checked on **every** activation, regardless of `permissions`), or, for a `guarded` run, a rule or `default` naming an `effect`/`action` outside the supported vocabulary. | Fix the `policy` block's shape and values; see [Job Session Object → Policy](/worker/session-agent-harness/thread-object). |
| `POLICY_MISSING`              | `agent.permissions` resolves to `guarded` but the thread carries no `policy` block.                                                                                                                                                        | Add a `policy` block to the envelope, or use `approval` / `autonomous` instead.                                            |
| `POLICY_AUTONOMOUS_FORBIDDEN` | `agent.permissions` resolves to `autonomous` but the worker's `policy.allow_autonomous` (in `config.yaml`) is `false`.                                                                                                                     | Use `approval` or `guarded`, or have the operator set `allow_autonomous: true`.                                            |
| `AGENT_EXECUTABLE_NOT_FOUND`  | Agent CLI (`claude` / `codex`) can't be resolved on PATH.                                                                                                                                                                                  | Install the agent; or override `agents.<type>.executable` in `config.yaml`.                                                |
| `AGENT_CRASHED`               | Agent process exited unexpectedly (exit code in `error.message`). A failed resume on restart also surfaces here.                                                                                                                           | Inspect `thread.log`; reset the thread to `pending` to retry.                                                              |
| `THREAD_POST_FAILED`          | Persistent 4xx posting items to the thread.                                                                                                                                                                                                | Check session access; inspect `thread.log` for the rejected payload.                                                       |
| `THREAD_ITEM_TOO_LARGE`       | An item exceeds the cap even after truncation (very rare).                                                                                                                                                                                 | Inspect `thread.log`; reduce tool output size; reset the thread.                                                           |

## Transient errors

Logged and retried with exponential backoff up to `polling.backoff_max_ms`. They surface in the TUI warning
ribbon and do **not** trigger any state-machine transition on their own (a long-running transient may
eventually present as a thread-level `THREAD_POST_FAILED` after exhaustion).

| Code                  | Cause                                 |
| --------------------- | ------------------------------------- |
| `API_TRANSIENT_ERROR` | 5xx response from the BlobHub API.    |
| `API_RATE_LIMITED`    | 429 response.                         |
| `API_NETWORK_ERROR`   | Connection failed or timed out.       |
| `API_COMMAND_FAILED`  | Generic 4xx-not-otherwise-classified. |

## Limitations

These trade-offs are specific to `session_agent_harness`. For generic worker limitations (no hot config
reload, single-process / single-machine), see [Reference](/worker/reference).

### `guarded` is a guardrail, not a sandbox

A `policy` (`permissions: guarded`) catches string/glob-matched mistakes; it is not OS-level confinement,
and of the two supported harnesses only `codex` has a sandbox underneath it — `claude_code` has none, so
there the policy is the only gate at all. See
[Job Session Object → Policy and guarded permissions](/worker/session-agent-harness/thread-object) for the
full statement, the per-harness capability table, what codex's sandbox refuses and escalates for a
`write` + `outside: work_folder` rule (including disguised escapes like hard links, the
`.git`/`.agents`/`.codex` carve-outs, and the no-sandbox-platform case), and why an `exec` rule governs
which commands run — never where they write — on either harness. Read it **before** writing rules for a
sensitive workspace, not
after discovering a gap the hard way.

### Same-user posting collision

A human posting into a thread via the BlobHub web UI **as the same user whose API key the worker uses** is
classified by the worker as "self" and ignored. The worker uses `user_id` as the only signal for filtering its
own emissions out of the inbound stream; there is no per-post "posted by worker" flag in v1. Run the worker
under a **dedicated service-account user** with its own API key so humans posting as themselves are seen as
not-self and reach the agent.

### No `detach` command

There is no `blobhub-worker detach` subcommand in v1. To free a session manually, delete the `worker` session
object directly:

```
delete_session_object(session_id, alias="worker")
```

The running worker observes the deletion event and stops that section with `SESSION_DETACHED_EXTERNALLY`.
Other sections continue.

### Same-user worker races

Two worker installs sharing the **same** API key (same `user_id`) will race to overwrite each other's `worker`
marker silently — both will run and both will post. One worker install = one service-account user with one API
key; don't share credentials across machines.

### Unavailable attachments never fail the thread

When a `file` attachment can't be materialized — the download fails after 3 attempts, or the thread's inbox
itself is unusable (an escaping `attachments.inbox` warns `inbox_invalid` and disarms attachment
materialization for that whole activation) — the worker doesn't fail the thread or the turn. It posts a
`status` / `attachment_unavailable` item naming what's missing and why, tells the agent the same thing in
its own prompt, and the turn proceeds without that file. See
[Thread Items → `attachment_unavailable`](/worker/session-agent-harness/thread-items#attachment_unavailable)
and [Job Session Object → Attachments](/worker/session-agent-harness/thread-object#attachments).

### No agent retry on failure

When an agent crashes or fails, the worker records the error in the local `thread.yaml`, transitions the
thread to `failed`, and posts a `thread_failed` activity item. It does **not** auto-retry. Inspect `thread.log`
/ the local `agent.error.message`, then update the envelope to set `instance.state = "pending"`; the worker
observes the change and re-runs the activation.

### Unresolved interactive prompts don't survive restart

If the worker is restarted while a `pending_prompt` is unanswered, the prompt is cancelled with
`pending_prompt_resolved` (`reason: worker_restart`). The agent re-asks on resume if it still needs the input.

### Multi-question prompt answering is best-effort

For `ask_user_question` prompts with multiple questions, the plain-text answer parser is best-effort
(line-prefix `1) ...`, `2) ...` format). Unparseable replies fall back to each question receiving the raw text
as a free-text answer. Answer one question at a time when the agent asks several at once.

### No native UI for thread metadata editing

To put a thread into `pending` state for the worker to pick up, you currently update the thread envelope's
`value.thread.metadata` directly via `upload_session_object` (REST or SDK) — for example:

```yaml theme={null}
workspace:
  work_folder: /absolute/path/to/repo
agent:
  type: claude_code
instance:
  state: pending
```

There is no inline editor in blobhub-web for these fields in v1; a dedicated editor is a deferred fast-follow.

## See also

* [Reference](/worker/reference)
* [Recovery](/worker/session-agent-harness/recovery)
* [Handoff](/worker/session-agent-harness/thread-handoff)
