Error Code Reference¶
SynthOrg's REST API emits RFC 9457 Problem Details responses on every error path. Every response carries three machine-readable fields that clients can discriminate on:
type: a stable URI describing the error categoryerror_code: an integer in one of the ranges belowerror_category: the category name matching the URI slug
Clients should dispatch on error_code (most specific) and fall back to error_category for generic handling. Messages and titles are human-readable and may change without notice; the code is the contract.
This page is the developer-oriented reference for the problem-type URIs and the NotFoundError class hierarchy. For the response surface and content negotiation an API consumer sees, see the Error Reference.
Category URIs¶
| Category | type URI |
Code range |
|---|---|---|
| Authentication / authorisation | https://synthorg.io/docs/errors#auth |
1000-1999 |
| Request validation | https://synthorg.io/docs/errors#validation |
2000-2999 |
| Resource not found | https://synthorg.io/docs/errors#not_found |
3000-3999 |
| Conflict / duplicate | https://synthorg.io/docs/errors#conflict |
4000-4999 |
| Rate limit / concurrency | https://synthorg.io/docs/errors#rate_limit |
5000-5999 |
| Budget exhausted | https://synthorg.io/docs/errors#budget_exhausted |
6000-6999 |
| Provider / integration failure | https://synthorg.io/docs/errors#provider_error |
7000-7999 |
| Internal / service unavailable | https://synthorg.io/docs/errors#internal |
8000-8999 |
Authentication (1xxx)¶
| Code | Name | When |
|---|---|---|
| 1000 | UNAUTHORIZED |
Missing or invalid session |
| 1001 | FORBIDDEN |
Authenticated but not permitted |
| 1002 | SESSION_REVOKED |
Session revoked by operator or user |
| 1003 | ACCOUNT_LOCKED |
Too many failed login attempts |
| 1004 | CSRF_REJECTED |
CSRF double-submit failed |
| 1005 | REFRESH_TOKEN_INVALID |
Refresh rotation mismatch or expired |
| 1006 | SESSION_LIMIT_EXCEEDED |
Per-user session cap reached |
| 1007 | TOOL_PERMISSION_DENIED |
Agent not permitted to invoke the tool |
| 1008 | SESSION_NO_TOKEN |
No session cookie or bearer token on the request |
| 1009 | SESSION_EXPIRED |
Session cookie / JWT decoded but past expiry |
Validation (2xxx)¶
| Code | Name | When |
|---|---|---|
| 2000 | VALIDATION_ERROR |
Generic validation failure |
| 2001 | REQUEST_VALIDATION_ERROR |
Litestar-parsed body/params rejected |
| 2002 | ARTIFACT_TOO_LARGE |
Upload exceeds artifact.max_bytes |
| 2003 | TOOL_PARAMETER_ERROR |
Tool parameters failed schema validation |
| 2004 | PROVIDER_TIER_COVERAGE_INSUFFICIENT |
Setup wizard cannot apply a template because no configured provider exposes any models |
| 2005 | IMMUTABLE_FIELD_MISMATCH |
A restore/rollback would change an immutable field (e.g. agent id/name/department) |
| 2006 | CHECKPOINT_ROLLBACK_UNAVAILABLE |
Fine-tune checkpoint rollback target is missing or unusable |
| 2007 | CHECKPOINT_ROLLBACK_CORRUPT |
Fine-tune checkpoint rollback backup data is corrupt |
| 2008 | RUN_HARD_CEILING_TOO_LOW |
Approved run hard-ceiling below the minimum |
| 2009 | LIVING_DOC_VALIDATION_ERROR |
Living-doc payload failed validation |
| 2010 | KNOWLEDGE_VALIDATION_ERROR |
Knowledge-source payload failed validation |
| 2011 | RESEARCH_VALIDATION_ERROR |
Research request failed validation |
| 2012 | BRAIN_ENTRY_VALIDATION_ERROR |
Project-brain entry failed validation |
| 2013 | STEERING_KIND_INVALID |
Steering directive kind not recognised |
| 2014 | STEERING_DIRECTIVE_FIELD_BLANK |
Steering directive required field blank |
| 2015 | STEERING_TASK_PROJECT_MISMATCH |
Steering task does not belong to the project |
| 2016 | ESCALATION_DECISION_INVALID |
Human-supplied escalation decision is invalid or not accepted by the active processor |
| 2017 | GIT_BACKEND_CONFIG_INVALID |
Git-backend configuration invalid for the selected strategy |
| 2018 | ENVIRONMENT_CONFIG_INVALID |
Reproducible-environment configuration invalid for the selected strategy |
| 2019 | WORKFLOW_DEFINITION_INVALID |
Workflow definition failed validation at activation time |
| 2020 | WORKFLOW_CONDITION_EVAL_FAILED |
Workflow condition expression could not be evaluated |
| 2021 | SUBWORKFLOW_IO_INVALID |
Subworkflow input/output binding is invalid |
| 2022 | WORKFLOW_TYPE_INVALID |
Request specified an unknown workflow_type value |
| 2023 | WORKFLOW_DEFINITION_VALIDATION_FAILED |
Workflow definition failed structural checks on create/update |
| 2024 | WORKFLOW_YAML_EXPORT_FAILED |
Workflow definition could not be serialised to YAML |
| 2025 | RED_TEAM_REPORT_INVALID |
Red-team report payload failed structural validation |
Not Found (3xxx)¶
The NotFound hierarchy is rooted at NotFoundError. Each resource has a dedicated subclass that pins the right ErrorCode as a ClassVar (for example TaskNotFoundError in synthorg.engine.errors, ConnectionNotFoundError in synthorg.integrations.errors, and ResourceNotFoundError in synthorg.core.domain_errors as the 3000 fallback). Controllers raise the subclass directly, or via require_resource_or_404(..., error_class=...), so the wire error_code is fixed by the class rather than a runtime argument.
| Code | Name | Resource |
|---|---|---|
| 3000 | RESOURCE_NOT_FOUND |
Fallback: the resource type isn't in the table below |
| 3001 | RECORD_NOT_FOUND |
Generic DB row not found |
| 3002 | ROUTE_NOT_FOUND |
HTTP path had no handler |
| 3003 | PROJECT_NOT_FOUND |
Project |
| 3004 | TASK_NOT_FOUND |
Task |
| 3005 | SUBWORKFLOW_NOT_FOUND |
Sub-workflow definition |
| 3006 | WORKFLOW_EXECUTION_NOT_FOUND |
Workflow execution record |
| 3007 | CHANNEL_NOT_FOUND |
Communication channel |
| 3008 | TOOL_NOT_FOUND |
Registered tool |
| 3009 | ONTOLOGY_NOT_FOUND |
Ontology entry |
| 3010 | CONNECTION_NOT_FOUND |
Integration connection |
| 3011 | MODEL_NOT_FOUND |
Provider model |
| 3012 | ESCALATION_NOT_FOUND |
Escalation queue entry |
| 3013 | WORKFLOW_DEFINITION_NOT_FOUND |
Workflow definition record |
| 3014 | AB_TEST_NOT_FOUND |
A/B test record for a proposal |
| 3015 | BACKUP_NOT_FOUND |
Backup archive |
| 3016 | MEMORY_ENTRY_NOT_FOUND |
Agent memory entry |
| 3017 | CONVERSATION_NOT_FOUND |
Conversation record |
| 3018 | LIVING_DOC_NOT_FOUND |
Living document |
| 3019 | KNOWLEDGE_SOURCE_NOT_FOUND |
Knowledge source |
| 3020 | RESEARCH_RUN_NOT_FOUND |
Research run record |
| 3021 | CHARTER_NOT_FOUND |
Company charter |
| 3022 | BRAIN_ENTRY_NOT_FOUND |
Project-brain entry |
| 3023 | DELIVERABLE_RECEIPT_NOT_FOUND |
Deliverable receipt |
| 3024 | API_KEY_NOT_FOUND |
API key |
| 3025 | UPGRADE_RECOMMENDATION_NOT_FOUND |
Upgrade recommendation |
| 3026 | GROUP_PARTICIPANT_UNKNOWN |
Group-chat participant not a known member |
| 3027 | CHECKPOINT_NOT_FOUND |
Fine-tune checkpoint |
| 3028 | FINE_TUNE_RUN_NOT_FOUND |
Fine-tune run record |
| 3029 | AGENT_NOT_FOUND |
Registered agent |
| 3030 | PERSONALITY_NOT_FOUND |
Agent personality |
| 3031 | TRAINING_SESSION_NOT_FOUND |
Training session record |
All share the same type URI; the numeric code is the discriminator.
Conflict (4xxx)¶
| Code | Name | When |
|---|---|---|
| 4000 | RESOURCE_CONFLICT |
Generic 409 (resource state mismatch) |
| 4001 | DUPLICATE_RECORD |
Unique-constraint violation |
| 4002 | VERSION_CONFLICT |
Optimistic-concurrency (ETag) mismatch |
| 4003 | TASK_VERSION_CONFLICT |
Same, scoped to a task update |
| 4004 | ONTOLOGY_DUPLICATE |
Duplicate ontology entity or alias |
| 4005 | CHANNEL_ALREADY_EXISTS |
Channel name already taken |
| 4006 | ESCALATION_ALREADY_DECIDED |
Late decision on a closed escalation |
| 4007 | MIXED_CURRENCY_AGGREGATION |
Cross-currency aggregation attempted |
| 4008 | WORKFLOW_EXECUTION_ALREADY_TERMINAL |
Cancel hit an execution already in a terminal status (no retry will succeed) |
| 4009 | BACKUP_IN_PROGRESS |
A backup/restore operation is already running |
| 4010 | CHECKPOINT_OPERATION_CONFLICT |
Checkpoint deploy/delete rejected (e.g. active checkpoint) |
| 4011 | FINE_TUNE_RUN_ACTIVE |
A fine-tune run is already active (start/resume blocked) |
| 4012 | TRAINING_PLAN_NOT_MODIFIABLE |
Training plan cannot be modified after execution or failure |
| 4013 | BACKUP_UNRESTARTABLE |
Backup service stopped in an unrestartable state |
| 4014 | AGENT_RUNTIME_NOT_CONFIGURED |
No LLM provider configured; agent runtime cannot execute |
| 4015 | CONVERSATION_CLOSED |
Conversation is closed; no further messages or actions accepted |
| 4016 | PROJECT_WORKSPACE_NOT_PROVISIONED |
Project workspace required but never provisioned by the git backend |
| 4017 | LIVING_DOC_VERSION_CONFLICT |
Living-doc write lost an optimistic-concurrency race |
| 4018 | ENVIRONMENT_BACKEND_UNAVAILABLE |
Declaration needs a sandbox backend that is not active (e.g. devcontainer on the subprocess backend) |
| 4019 | CHARTER_ALREADY_DECIDED |
Late decision on an already-decided charter |
| 4020 | CHARTER_NOT_EDITABLE |
Charter cannot be edited in its current state |
| 4021 | BRAIN_ENTRY_REVISION_CONFLICT |
Project-brain entry optimistic-concurrency clash |
| 4022 | HIRING_APPROVAL_REQUIRED |
Hiring request needs approval before instantiation |
| 4023 | HIRING_REJECTED |
Hiring request was rejected |
| 4024 | AGENT_ALREADY_REGISTERED |
Agent is already registered in the registry |
| 4025 | PROMOTION_COOLDOWN_ACTIVE |
Promotion blocked by an active cooldown window |
| 4026 | PROMOTION_APPROVAL_REQUIRED |
Promotion needs human approval before proceeding |
| 4027 | PRUNING_UNRESTARTABLE |
Pruning service is unrestartable after a timed-out stop |
| 4028 | SCALING_COOLDOWN_ACTIVE |
Scaling action blocked by an active cooldown window |
| 4029 | ROLLBACK_MUTATION_DENIED |
Rollback mutator's underlying store refused the write |
| 4030 | UPGRADE_RECOMMENDATION_ALREADY_DECIDED |
Approving/rejecting an already-decided recommendation |
| 4031 | TURN_SEQUENCE_CONFLICT |
Conversation-turn append lost the (conversation_id, sequence) uniqueness race after the retry budget (retryable) |
Rate Limit (5xxx)¶
| Code | Name | When |
|---|---|---|
| 5000 | RATE_LIMITED |
Global per-user / per-IP throttle tripped |
| 5001 | PER_OPERATION_RATE_LIMITED |
Specific operation's (max_requests, window) budget exhausted |
| 5002 | CONCURRENCY_LIMIT_EXCEEDED |
Too many in-flight requests for the op |
| 5003 | AGENT_CONNECTION_LIMIT_EXCEEDED |
Message-bus quadratic hard_block rejected a new agent connection |
| 5004 | WS_TICKET_LIMIT_EXCEEDED |
Per-user pending WebSocket-ticket cap reached (consume or let tickets expire) |
Budget Exhausted (6xxx)¶
| Code | Name | When |
|---|---|---|
| 6000 | BUDGET_EXHAUSTED |
Company-level budget hard stop |
| 6001 | DAILY_LIMIT_EXCEEDED |
Prorated daily cap tripped |
| 6002 | RISK_BUDGET_EXHAUSTED |
Per-risk-tier budget exceeded |
| 6003 | PROJECT_BUDGET_EXHAUSTED |
Project-scoped budget hard stop |
| 6004 | QUOTA_EXHAUSTED |
Metered feature quota reached |
| 6005 | COST_FORECAST_APPROVAL_REQUIRED |
Run blocked pending cost-forecast approval |
| 6006 | RUN_HARD_CEILING_EXCEEDED |
Per-brief hard ceiling exceeded |
| 6007 | COST_FORECAST_REJECTED |
Cost forecast was rejected |
| 6008 | RESEARCH_BUDGET_EXCEEDED |
Research budget exhausted |
Provider / Integration (7xxx)¶
| Code | Name | When |
|---|---|---|
| 7000 | PROVIDER_ERROR |
Generic upstream failure |
| 7001 | PROVIDER_TIMEOUT |
Upstream timed out |
| 7002 | PROVIDER_CONNECTION |
Network-level failure |
| 7003 | PROVIDER_INTERNAL |
Provider returned 5xx |
| 7004 | PROVIDER_AUTHENTICATION_FAILED |
Invalid credentials |
| 7005 | PROVIDER_INVALID_REQUEST |
Provider rejected the request |
| 7006 | PROVIDER_CONTENT_FILTERED |
Provider filtered the content |
| 7007 | INTEGRATION_ERROR |
Non-LLM integration failure |
| 7008 | OAUTH_ERROR |
OAuth exchange failed |
| 7009 | WEBHOOK_ERROR |
Webhook receive/replay failure |
| 7010 | CONVERSATIONAL_PROPOSE_RESPONSE_INVALID |
Chief-of-Staff proposer returned an invalid response |
| 7011 | CHARTER_INTERVIEW_RESPONSE_INVALID |
Charter interview agent returned an invalid response |
Internal (8xxx)¶
| Code | Name | When |
|---|---|---|
| 8000 | INTERNAL_ERROR |
Unspecified server error |
| 8001 | SERVICE_UNAVAILABLE |
Dependent service not wired yet |
| 8002 | PERSISTENCE_ERROR |
DB-level failure |
| 8003 | ENGINE_ERROR |
Engine-layer failure |
| 8004 | ONTOLOGY_ERROR |
Ontology subsystem failure |
| 8005 | COMMUNICATION_ERROR |
Meeting/message bus failure |
| 8006 | TOOL_ERROR |
Generic tool failure |
| 8007 | ARTIFACT_STORAGE_FULL |
Artifact store at capacity |
| 8008 | TOOL_EXECUTION_ERROR |
Tool runtime failure (subclass of TOOL_ERROR) |
| 8009 | FEATURE_NOT_IMPLEMENTED |
Active backend or deployment fundamentally does not implement the requested operation (501) |
| 8010 | ARTIFACT_NO_STORAGE_BACKEND |
Artifact service was constructed without a storage backend; controller-helper misconfiguration |
| 8011 | AGENT_IDENTITY_ROLLBACK_FAILED |
Unexpected server failure during agent-identity rollback |
| 8012 | BACKUP_RESTORE_FAILED |
Restore operation failed (non-recoverable backend error) |
| 8013 | BACKUP_MANIFEST_ERROR |
Backup manifest could not be parsed or validated |
| 8014 | SETTINGS_ENCRYPTION_ERROR |
Internal error processing a sensitive (encrypted) setting |
| 8015 | SINK_CONFIG_VALIDATION_ERROR |
Internal error validating an observability sink configuration |
| 8016 | WORKER_DEAD_LETTER_ERROR |
Worker dead-letter handling failed |
| 8017 | LIVING_DOC_INDEX_ERROR |
Living-doc RAG index operation failed |
| 8018 | LIVING_DOC_COMMIT_ERROR |
Living-doc commit to the workspace failed |
| 8019 | KNOWLEDGE_INGEST_ERROR |
Knowledge-source ingestion failed |
| 8020 | KNOWLEDGE_RETRIEVAL_ERROR |
Knowledge retrieval failed |
| 8021 | KNOWLEDGE_DEPENDENCY_ERROR |
Knowledge subsystem dependency unavailable |
| 8022 | KNOWLEDGE_SOURCE_UNAVAILABLE |
Requested knowledge source is unavailable |
| 8023 | ENVIRONMENT_ERROR |
Generic reproducible-environment failure |
| 8024 | ENVIRONMENT_PROVISION_FAILED |
Environment setup/provisioning failed |
| 8025 | ENVIRONMENT_DOCKER_BUILD_FAILED |
Devcontainer image build failed |
| 8026 | RESEARCH_RUN_ERROR |
Research run failed |
| 8027 | RESEARCH_RETRIEVAL_ERROR |
Research retrieval failed |
| 8028 | RESEARCH_SYNTHESIS_ERROR |
Research synthesis failed |
| 8029 | RESEARCH_UNAVAILABLE |
Research subsystem unavailable |
| 8030 | FEATURE_DEPENDENCY_ERROR |
Required feature dependency unavailable |
| 8031 | BRAIN_INDEX_ERROR |
Project-brain index operation failed |
| 8032 | BRAIN_COMMIT_ERROR |
Project-brain commit to workspace failed |
| 8033 | NARRATIVE_GENERATION_ERROR |
Narrative generation failed |
| 8034 | NARRATIVE_SOURCE_UNAVAILABLE |
Narrative source data unavailable |
| 8035 | DELIVERABLE_RECEIPT_BUILD_ERROR |
Deliverable receipt build failed |
| 8036 | SINK_CONSTRUCTION_ERROR |
Observability sink construction failed |
| 8037 | SUBWORKFLOW_CYCLE_ERROR |
Subworkflow reference graph contains a cycle |
| 8038 | SUBWORKFLOW_DEPTH_EXCEEDED_ERROR |
Runtime subworkflow nesting exceeded the configured limit |
| 8039 | TASK_ENGINE_NOT_RUNNING |
Task engine is not running; submission rejected (503) |
| 8040 | TASK_ENGINE_QUEUE_FULL |
Task engine admission queue is full (503) |
| 8041 | CONVERSATIONAL_PROPOSE_UNAVAILABLE |
Chief-of-Staff propose mode not wired (503) |
| 8042 | GROUP_CHAT_UNAVAILABLE |
Group-chat mode not wired (503) |
| 8043 | CHARTER_INTERVIEW_UNAVAILABLE |
Charter interview mode not wired (503) |
| 8044 | KNOWLEDGE_SYNTHESIS_ERROR |
Knowledge synthesis failed |
| 8045 | KNOWLEDGE_SYNTHESIS_UNAVAILABLE |
Knowledge synthesis not wired (503) |
| 8046 | RED_TEAM_REPORT_MISSING |
Red-team agent filed no report for the deliverable |
| 8047 | RED_TEAM_DISPATCH_FAILED |
Red-team agent dispatch failed before a report |
| 8048 | RED_TEAM_ROLE_MISSING |
Built-in red-team role absent from the catalog |
| 8049 | RED_TEAM_RUNTIME_SEED_INCOMPLETE |
Red-team runtime seed incomplete (wiring fault) |
| 8050 | BACKUP_COMPONENT_FAILED |
A per-component backup or restore step failed |
| 8051 | BACKUP_RETENTION_FAILED |
Backup retention pruning failed |
| 8052 | BACKUP_CONFIGURATION_INVALID |
Backup handler configuration is invalid |
| 8053 | CI_HOST_EXECUTION_REFUSED |
CI validator refused host execution (container required) |
Content negotiation¶
Clients that set Accept: application/problem+json receive a bare RFC 9457 body. Clients that accept application/json receive an ApiResponse envelope with error_detail carrying the same fields. See the API reference for per-route examples.
HTTP exception handler registration (MANDATORY)¶
Litestar resolves exception handlers by walking the raised exception's
MRO; the first matching type in EXCEPTION_HANDLERS
(src/synthorg/api/exception_handlers.py) wins. Domain error families
register a single base-class handler (e.g. BackupError,
PersistenceError, OntologyError) so every subtype maps to a
structured response without falling through to the catch-all
Exception: handle_unexpected (which would surface as a generic 500
without a domain-specific error_code).
Controllers MUST NOT catch a domain error and build their own
Response(...) envelope. Raise the typed domain error (declaring
status_code / error_code / error_category ClassVars on the
class) and let handle_domain_error produce the RFC 9457 envelope.
Enforced at pre-push by
scripts/check_no_controller_response_for_domain_errors.py.
Per-line opt-out:
# lint-allow: controller-domain-response -- <reason>.
Controller error re-mapping¶
Class metadata is the single source of truth for the wire contract; a
controller MUST NOT catch a domain error and re-raise it with a
different HTTP status or error_code. Doing so drops the originating
error's retryable signal and its discriminating code (see
ADR-0014).
- Business-rule vs transient-backend. When one call site can fail
for a business reason or a transient backend reason, raise a distinct
typed error for the business case so the contract reflects the cause.
The checkpoint service raises
CheckpointActiveConflictError(409, non-retryable) when rejecting a delete/deploy of the active checkpoint, while a transientQueryError(500, retryable) flows tohandle_persistence_erroruntouched. Controllers must not collapse the transientQueryErrorinto the 409. - Let retryable upstream failures propagate.
oauth.pydoes not catchTokenExchangeFailedError; its 502 + retryable metadata propagates so the client learns the upstream token endpoint failed and may retry. - Intentional uniform-404 (security override).
connections.reveal_secretraisesSecretRetrievalNotFoundError(404,RESOURCE_NOT_FOUND) for both "connection absent" and "secret absent". Returning the same 404 either way prevents a caller from enumerating which connection names exist by probing secret-reveal error codes. This uniform-404 is deliberate; do not "fix" it into a more specific code, which would re-introduce the enumeration leak.
Each handler:
- Calls
_log_error(request, exc, status=...)for structured logging (WARNING for 4xx, ERROR + traceback for 5xx). - Returns
_build_response(...)so the response carries the full RFC 9457 envelope (or bareapplication/problem+jsonbody when the client asks for it). - Scrubs the message. On 5xx, handlers return the class-level
default_messageso no internal detail leaks. On 4xx,_select_message(and thehandle_validation_error/handle_http_exception/handle_invalid_cursorhandlers) routes the exception message throughscrub_secret_tokensas a defence-in-depth pass: a credential or token a raise site interpolated is redacted, while ordinary validation text is untouched. This central scrub is why domain errors propagate straight tohandle_domain_errorwith no controller-level catch-and-sanitise. Several Litestar-side handlers go further and return fixed public messages (handle_record_not_found->"Resource not found",handle_not_authorized->"Authentication required",handle_permission_denied->"Forbidden",handle_not_found->"Not found"). When in doubt, mirror the nearest existing handler inexception_handlers.py.
When introducing a new domain error family:
- Add the base class to
EXCEPTION_HANDLERSmapped to a dedicatedhandle_<domain>_errorfunction. - Use
isinstancedispatch inside the handler to map subtypes to specific HTTP status codes (404,409, etc.) before falling through to the structured500branch. - Register the entry above the catch-all
Exception: handle_unexpectedline in the dict; MRO ordering does not depend on dict insertion order, but readability does. - Add tests in
tests/unit/api/test_exception_handlers.pycovering each branch and a regression test for the catch-all.
Domain-error-hierarchy gate¶
The check_domain_error_hierarchy.py gate that enforces this rule (no
stdlib-rooted exception subclasses under src/synthorg/) is documented in
Convention Gates.
Error-code uniqueness gate¶
Each ErrorCode maps to exactly one DomainError subclass, so a client
branching on error_code can always tell two conditions apart. The one
exception is an inheritance alias: a subclass that deliberately keeps
an ancestor's code (for example ProviderModelNotFoundError, which
subclasses ModelNotFoundError and inherits MODEL_NOT_FOUND 3011, and
the NotFoundError subclasses that share RESOURCE_NOT_FOUND 3000 as
the fallback). Two unrelated classes declaring the same code is a
violation.
scripts/check_error_code_uniqueness.py enforces this at pre-push and in
CI. For each concrete DomainError subclass it collects the
(class, error_code, bases) triple by AST across every *.py file
under src/synthorg/ (subclasses can live in any module, not only
errors.py), groups by code, and fails a group unless its members
share a common DomainError
ancestor declaring that code, or the code is one of the shareable generic
codes (INTERNAL_ERROR, VALIDATION_ERROR, RESOURCE_NOT_FOUND,
RESOURCE_CONFLICT, RECORD_NOT_FOUND, ...). The gate is AST-only (no
import), uses git-ls-files discovery with a Path.rglob fallback, and
ships with zero offenders.
Per-line opt-out (reserved for documented twin classes that legitimately share a code, e.g. a persistence-layer mirror of a domain error):
class PersistenceVersionConflictError(QueryError): # lint-allow: error-code-uniqueness -- twin of domain VersionConflictError
...
The justification after -- is mandatory. See
ADR-0014 for the
rationale and the four duplicate-code pairs this gate locked down.
Error-code constants - frontend integration (MANDATORY)¶
src/synthorg/core/error_taxonomy.py is the single source of truth
for ErrorCode and ErrorCategory. The dashboard imports the same
constants from web/src/api/types/error-codes.gen.ts, which is
generated from the Python enums by
scripts/generate_error_codes_ts.py.
When adding or renaming a code:
- Edit
src/synthorg/core/error_taxonomy.py. - Run
uv run python scripts/generate_error_codes_ts.pyand commitweb/src/api/types/error-codes.gen.tsalongside the Python change. - Update any frontend call sites that newly want to discriminate on
the code (import
ErrorCodefrom@/api/types/errorsand use the named member, never the raw integer).
The pre-push gate scripts/check_error_codes_ts_in_sync.py re-runs
the generator and fails if the committed error-codes.gen.ts differs
byte-for-byte from the freshly rendered output, so a backend code
addition that forgets the regeneration step cannot land.
Frontend code MUST import error codes through @/api/types/errors
(re-exported from the generated module). Inlining numeric error codes
(e.g. error_code: 3000) is forbidden -- name-based discrimination
keeps the web/ and src/synthorg/ sides in lockstep when codes are
renumbered.
Interpreter-critical exception propagation¶
The broad-handler rule for propagating MemoryError / RecursionError
via synthorg.core.critical_errors.reraise_critical is documented in
Async concurrency conventions.
Further reading¶
- Design: security: the SEC-1 rules behind the categories
- REST API reference: per-route examples plus a worked content-negotiation walkthrough
src/synthorg/core/error_taxonomy.py: the authoritativeErrorCategory/ErrorCodeenums + RFC 9457 helperssrc/synthorg/core/domain_errors.py: theDomainErrorbase + concrete subclasses (NotFoundError,ConflictError,ValidationError, ...)src/synthorg/core/persistence_errors.py: thePersistenceErrorhierarchysrc/synthorg/core/critical_errors.py: thereraise_criticalhelper for the broad-except idiom