session_agent_harness job type: the error codes it can raise — in three
classes beyond the generic process codes 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 session tuple. | Remove one of the duplicates. |
Section-level errors
One section fails to attach to its session; the failure is persisted injobs/{job_id}/section.yaml.attachment.error and the section stops, while the worker keeps running all other
sections.
| Code | Cause | Remediation |
|---|---|---|
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). |
SESSION_NOT_FOUND | The session 404’d or the API user lacks access. | Check the (org_id, blob_id, revision_id, session_id) tuple and permissions. |
Thread-level errors
A single thread transitions tofailed, 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. | Use a supported value (default is approval). |
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 topolling.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 tosession_agent_harness. For generic worker limitations (no hot config
reload, single-process / single-machine), see Reference.
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 usesuser_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:
SESSION_DETACHED_EXTERNALLY.
Other sections continue.
Same-user worker races
Two worker installs sharing the same API key (sameuser_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.
No agent retry on failure
When an agent crashes or fails, the worker records the error in the localthread.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 apending_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
Forask_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 intopending 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:

