Skip to content

Errors

Branch on code, not on the message

Errors that need distinguishing carry a machine-readable code:

json
{ "detail": { "code": "active_version_changed", "message": "…", "current_active_sha": "83efc…" } }

Messages are written for humans and may be reworded. code values are part of the contract and will not change under you.

The publish endpoint returns two different 409s and several 422scode is how you tell them apart. Matching on the shape of the body works until the day it does not.

Every error

StatuscodeCauseWhat to do
401no key, or invalid or revokedcheck the key. Not on the read endpoint, which returns 404
403read-scoped key on a publishrequest a write key
403owner lacks the approver tag on a Manual review projectask the Instructions Manager team. Do not retry
403not a member of that project or organisation (listing)ask to be added to the project
404unknown project or prompt, no active version, or no accessverify the ids. Deliberately vague — no existence leak
409active_version_changedstale expected_active_share-fetch and reconcile. Never auto-retry
409llm_not_configuredai_assisted project with no AI provideroperator issue. Do not retry
409— (available_environments in body)read only: not released to that environmentpick a released environment
413content_too_largeover the size capshrink the content. Body carries limit_bytes and content_bytes
422review_suggestions_openthe reviewer agent has findingsfix the content, or re-send with acknowledge_review
422missing expected_active_sha without forcesend the sha, or force
422content mismatches kind, unknown kind, blank messagefix the payload
422both or neither scope on a listingpass exactly one
422skill_scan_blockedskill security scan blocked the contentread scan.findings. Not overridable
503review_unavailablethe reviewer agent could not runnothing published. Retry with backoff
5xxserver-sideretry with backoff

Retry policy

Retry: 5xx, and 503 review_unavailable. Those are transient, and in the 503 case nothing was published.

Never auto-retry: 401, 403, 404, 409, 413, 422. None of them resolve on their own, and two are actively harmful:

  • Retrying 409 active_version_changed overwrites another person's edit a moment later — the exact failure the guard exists to prevent.
  • Retrying 422 review_suggestions_open gets you the same answer.

The two that trip people up

DANGER

404 from the read endpoint does not mean "not found". Five causes — bad key, unknown project, unknown prompt, no active version, no access — return byte-identical bodies, so the endpoint cannot be used to enumerate what exists. Check the key first; it is the most common cause and the least obvious.

DANGER

A 403 on publish is a configuration change, not a bug in your code. The key's owner lost the approver tag, left the project, or the key was revoked. Nothing on your side changed. Alert an operator rather than looping — see Authentication.

Handling them

try:
    publish(content, message, expected_active_sha=base_sha)

except 422 where code == 'review_suggestions_open':
    show detail.suggestions to the user
    if they revise:   go back and edit
    if they override: publish(..., acknowledge_review=detail.review_id)

except 409 where code == 'active_version_changed':
    reload(detail.current_active_sha)
    tell the user the prompt changed underneath them

except 409 where code == 'llm_not_configured':
    alert an operator — the project needs an AI provider. Do not retry.

except 503 where code == 'review_unavailable':
    retry with backoff — nothing was published

except 413:
    shrink the content; detail names the limit

except 403:
    alert an operator; do not retry

Note the shape: the override path calls the same publish function with one extra argument. Resist building a separate "force publish" path — that is how an override becomes the default.

Next

Copyright © 2026 elsai foundry.