<!-- CODE GENERATED BY cmd/harbor-gen-protocol-docs. DO NOT EDIT. -->
Protocol errors
The 40 canonical Harbor Protocol error codes, generated from the single-source registry (internal/protocol/errors). The HTTP column is read from the same code-to-status binding the wire transport serves — the two cannot drift.
Every error response body is the one Error envelope:
json
{ "code": "<stable code>", "message": "<human-readable, advisory>" }Clients branch on code (stable across Runtime refactors — RFC §5.3), never on message.
| Code | HTTP | When it fires | Should you retry? |
|---|---|---|---|
agent_pack_copy_conflict | 409 | A same-runtime agent_config.agent_packs.copy operation was well-formed and its composition preconditions were current, but an independently authored target pack would be overwritten. The atomic copy was refused and no selected pack was partially applied. | No — inspect the target, resolve the authored collision explicitly, then choose a new reviewed copy request. |
agent_pack_copy_idempotency_conflict | 409 | A same-runtime Agent pack copy idempotency key was reused with a different source, target, selected-pack set, or composition precondition fingerprint. The replay is refused without mutation. | No — replay the original request with that key or choose a new idempotency key for a distinct copy. |
agent_retired | 409 | An authorized agent-addressed operation selected a terminally retired agent configuration. | No — choose a different agent; retirement is terminal. |
agent_retirement_conflict | 409 | A retirement request used a stale active-content hash or a different operation id from the durable replay identity. | Only by replaying agent_config.retire with the exact original operation id and expected content hash. |
auth_rejected | 401 | A bearer token was present but failed verification: malformed, an algorithm outside the asymmetric allowlist, bad signature, expired / not-yet-valid, unknown kid, audience or issuer mismatch. | Only after obtaining a fresh valid token. |
identity_required | 401 | The request resolved no complete (tenant, user, session) identity scope — a missing bearer, a missing session (no X-Harbor-Session header and no default claim), or a body identity that contradicts the verified token. Identity is mandatory and fails closed. | No — attach a token / session first (Auth & identity). |
identity_scope_required | 403 | The request is authenticated and identified, but the requested cross-tenant fan-in (e.g. events.subscribe?admin=1) or admin verb needs a verified admin / console:fleet scope claim the token does not carry. | No — re-authenticate with a scope-bearing token. |
invalid_cursor | 400 | The observability.query page cursor is malformed or was produced by a differently-shaped query (including a different identity scope). The query never silently restarts at an arbitrary position. | Yes — restart from the first page (no cursor). |
invalid_request | 400 | The request was structurally malformed: undecodable JSON, a wrong wire shape for the method, an out-of-range field. | No — fix the request shape first. |
not_found | 404 | The request's target does not exist in the caller's scope: a steering control for a run with no live inbox (never started or already terminal), an unknown task / flow / artifact id. Cross-tenant existence is never revealed — a foreign id is indistinguishable from a missing one. | No — the target is gone or never existed for you. |
payload_invalid | 422 | A control payload violated an RFC §6.3 bound (depth > 6, > 64 keys, > 50 list items, a string > 4096 chars, > 16 KiB total), carried an unsupported leaf type, or violated a control's shape (for example attachments on text-only user_message). | No — shrink / restructure the payload. |
presign_unsupported | 501 | An artifacts.get_ref request reached an ArtifactStore driver without presigned-URL support (in-mem / fs / sqlite / postgres blob drivers). The resolver fails loud instead of silently streaming bytes. | No — the configured driver cannot satisfy it; use a presign-capable store (S3 family) or download via the Console proxy. |
query_budget_exceeded | 400 | An observability.query request exceeds a result budget: the window spans more buckets than the closed maximum, or the page limit exceeds the maximum. Fails loudly; never a truncated response. | Yes — narrow the window, coarsen the bucket, or lower the page limit. |
render_admission_expired | 400 | The render-admission token is well-formed but its expiry is past. The App must re-read the resource to mint a fresh admission — an otherwise-current App with an expired admission never collapses to an ambiguous not-found. | Yes — re-read the resource for a fresh admission. |
render_admission_invalid | 400 | The render-admission token opened but its sealed claims are structurally invalid: unknown schema/version, bound violations, a malformed nonce, an absurd lifetime, or future issuance. | No — re-read the resource for a fresh admission. |
render_admission_mismatch | 400 | The render-admission token is well-formed and time-valid but does not match the requested render tuple (identity / agent / server / resource URI / current descriptor generation). The error names no dimension. | No — re-read the exact resource under the same identity/agent for a fresh admission. |
render_admission_missing | 400 | An mcp.apps.call_tool request carried a render-admission authority but the value was empty, or the request referenced one that was not supplied. The MCP Apps render admission is the HA-56 sealed authority minted only by a successful opt-in ui:// read. | No — re-read the resource with request_render_admission: true and echo the returned token. |
render_admission_unavailable | 400 | The supplied render-admission token could not be opened: invalid base64url, an oversized token, envelope tamper, or a different sealing key/replica. The token's content is unrecoverable by design. | Yes — re-read the resource for a fresh admission; the App itself is still current. |
render_authority_ambiguous | 400 | An mcp.apps.call_tool request supplied BOTH the legacy binding authority and the fresh render_admission authority. The two are distinct; Harbor never guesses which the App meant. | No — supply exactly one authority. |
request_too_large | 413 | An artifacts.put body exceeded the configured protocol.max_request_bytes bound. The upload is refused loudly, never truncated. | No — shrink the payload or raise the operator-side bound. |
restart_unavailable | 409 | A persisted tranche pause has no live in-process run loop capable of exact restart redrive. Harbor fails closed rather than pretending to continue the run as a new task. | No — exact restart redrive is unavailable; start a new run instead. |
retained_context_unavailable | 409 | The own-session retained evidence is missing, expired, erased, corrupt or changed concurrently. No partial context is published. | Only after resolving the evidence/state conflict. Never repeat a side effect based on this error. |
retained_context_unsettled | 409 | Explicit retained-context reconciliation found a pending external operation. The outcome is unknown, not failed; no historical action is retried. | Do not retry execution. Reconcile the external operation with its owning service; retry context reconciliation only after a trustworthy settlement is recorded. |
revision_conflict | 409 | An agent_config.* write declared an expected_content_hash and the agent's active revision no longer carries it — another writer moved the base between the caller's read and its write — or the agent has no active revision at all. The request was well-formed and authorised; nothing was persisted (no revision, no active-pointer move, no agent.config.revised event). The refusal is exact across Runtime processes sharing a shipped StateStore: publication rechecks the active-pointer EventID through StateStore.SaveIf; the per-owner lock only reduces local contention. Omitting expected_content_hash keeps the unconditional last-writer-wins behaviour. | Yes, after re-reading — call agent_config.get (or agent_config.user.get if the door you are retrying is a user.* twin; they are separate revision spines and a hash from the wrong one never matches) for the current revision_id and content_hash, re-apply your edit on top (agent_config.diff compares what you read against what it is now), and resubmit with the fresh hash. |
runtime_error | 500 | An unclassified runtime-side failure — the catch-all. Also used on the SSE surface for subscriber-limit (429) and bus-closed (503) conditions. | Yes, with backoff — the request shape is not the problem. |
scope_mismatch | 403 | The caller's steering scope claim is below the control method's RFC §6.3 minimum, or a cross-tenant steering / mutation was attempted without admin. | No — the operation needs a higher scope. |
session_erased | 409 | A start named a session id that was permanently deleted by sessions.delete (right-to-erasure). The session is terminal and cannot be reopened — its data is gone. A closed-but-not-erased session, by contrast, reopens normally on start. | No — the conversation was erased; start a new one with a fresh session id. |
session_running | 409 | A sessions.delete erasure was refused because the target session has a RUNNING task, mirroring the GC never-reap-running invariant. No store is touched on refusal — a session with in-flight work is durable execution state, not a cache entry. | Yes — re-issue after the session's task finishes (or cancel it first). |
session_skill_cutover_pending | 409 | A session-personal skill mutation reached a tenant whose explicit durable cutover is still dual_read; Harbor refuses the mutation until the declared migration completes and a fresh verification pass authorizes state_only. | Yes, after the operator completes the tenant's declared cutover; do not retry as an unconditional legacy write. |
session_skill_read_unstable | 409 | All three bounded before/after lifecycle and session-erasure fence reads observed concurrent change. Harbor returned no partial session-skill view. | Yes, after the concurrent lifecycle or erasure transition settles. |
skill_import_package_invalid | 400 | The referenced artifact is not a valid complete skill package (archive / path / MIME / SKILL.md / support-ref / frontmatter violations, including authority-bearing frontmatter). The request envelope was well formed; the package was not. | No — fix the package and re-upload, then re-validate. |
skill_import_proposal_expired | 400 | The echoed import proposal token's review window elapsed before the explicit commit. | Yes — re-run agent_config.user.skills.import_validate for a fresh proposal. |
skill_import_proposal_invalid | 400 | An agent_config.user.skills.import_commit echoed a proposal token that is unknown, consumed, foreign, or stale (oversize, malformed base64, failed authentication, unknown schema, malformed claims, or bound to different server-side inputs). | Yes — re-run agent_config.user.skills.import_validate for a fresh proposal and echo it exactly. |
skill_import_replace_required | 409 | A different package already wins the target canonical key and the commit did not carry explicit replacement consent. | Yes — re-review the existing winner and commit again with replace: true. |
skill_publication_conflict | 409 | An HA-68 publication or Agent reference mutation used a stale exact generation/content-hash precondition. | Yes, after re-reading the current publication or reference and resubmitting the reviewed change with fresh exact preconditions. |
skill_publication_idempotency_conflict | 409 | An HA-68 idempotency key was reused with a different mutation request. | No — use the original request for replay or choose a new idempotency key for a distinct mutation. |
skill_publication_not_found | 404 | An HA-68 publication or exact Agent reference was not found in the caller's same-runtime scope. | No — re-read the same-runtime metadata and use an existing exact identifier. |
skill_publication_retired | 409 | An HA-68 request attempted to use a terminally retired publication revision. | No — choose an active publication; retirement is terminal. |
skill_publication_runtime_mismatch | 409 | An HA-68 publication or reference belongs to another Harbor runtime/deployment binding. | No — same-runtime publication references are not portable; discover a reference on this Runtime. |
unknown_method | 404 | The method name is not in the canonical registry (methods.md). | No — check the method name and this Runtime's advertised capabilities (runtime.info). |