Skip to main content

Error Codes

SochDB uses a cross-language error taxonomy with machine-readable numeric codes. The same code range means the same thing in every SDK, so you can switch on error.code (Python / Node.js) or match on a sentinel (Go) and write remediation logic that ports between languages.

Coverage differs per SDK

The taxonomy is defined most completely in the Python SDK (sochdb 0.5.9), which ships the full ErrorCode IntEnum and a deep exception hierarchy. The Node.js SDK (@sochdb/sochdb 0.5.3) defines a subset of codes plus typed error classes, and the Go SDK (github.com/sochdb/sochdb-go 0.4.5) exposes sentinel error values plus error structs with Is() matchers. The numeric values that exist in more than one SDK are identical.

Code ranges at a glance

Every code is a 4- or 5-digit integer. The leading digits identify the category.

RangeCategoryMeaningFirst-line remediation
1xxxConnection / transportCould not reach or stay connected to the server or open the embedded engineCheck the address/path, retry with backoff, verify the server is running
2xxxTransactionTransaction aborted, conflicted, expired, or not foundRetry the transaction; reduce write contention
3xxxNamespace / scopeNamespace missing, duplicate, invalid name, read-only, or access deniedCreate the namespace first, or check tenant scope
4xxxCollectionCollection missing, duplicate, frozen, or misconfiguredCreate the collection, or fix its config
5xxxQueryQuery invalid, timed out, cancelled, or too largeAdd filters, paginate, raise the timeout
6xxxValidationBad vector dimension, metadata, id, filter, or missing fieldFix the request payload before retrying
7xxxResourceNot found, already exists, quota or resource exhaustedBack off, free resources, or pick another id
8xxxAuthorizationUnauthorized, forbidden, token expired, scope violationCheck the capability token / credentials
9xxxInternalInternal error, not implemented, storage error, FFI errorFile a bug with the message and context
10xxxLock / concurrencyDatabase locked, lock timeout, epoch mismatch, split-brain, stale lockEnsure a single active writer; re-open the database
Validation vs. scope codes

ScopeViolationError is a validation class in Python but carries the authorization code 8004 (SCOPE_VIOLATION). Switch on the numeric code when you need the exact category; switch on the exception type when you want the semantic grouping.

Python SDK (0.5.9)

All exceptions are importable from the top-level sochdb package and derive from SochDBError, which carries .code (an ErrorCode), .message, .remediation, and .context. SochDBError.to_dict() gives you a JSON-serializable record.

from sochdb import SochDBError, ErrorCode, TransactionConflictError

try:
txn.commit()
except TransactionConflictError as e:
# Typed catch: retry on SSI conflict
print(e.code) # ErrorCode.TRANSACTION_CONFLICT
print(int(e.code)) # 2002
print(e.remediation) # "Retry the transaction. ..."
print(e.to_dict()) # {"code": 2002, "code_name": "TRANSACTION_CONFLICT", ...}
except SochDBError as e:
# Catch-all: branch on the numeric range
if 1000 <= e.code < 2000:
reconnect()
raise

ErrorCode IntEnum

These are the exact values defined in sochdb/errors.py. The enum is an IntEnum, so ErrorCode.QUERY_TIMEOUT == 5002 and int(ErrorCode.QUERY_TIMEOUT) == 5002.

CodeNameMapped exception
1001CONNECTION_FAILEDConnectionError
1002CONNECTION_TIMEOUTConnectionError*
1003CONNECTION_CLOSEDConnectionError*
1004PROTOCOL_ERRORProtocolError
2001TRANSACTION_ABORTEDTransactionError
2002TRANSACTION_CONFLICTTransactionConflictError
2003TRANSACTION_EXPIREDTransactionError*
2004TRANSACTION_READ_ONLYTransactionError*
2005TRANSACTION_NOT_FOUNDTransactionError*
3001NAMESPACE_NOT_FOUNDNamespaceNotFoundError
3002NAMESPACE_ALREADY_EXISTSNamespaceExistsError
3003NAMESPACE_INVALID_NAMENamespaceError*
3004NAMESPACE_ACCESS_DENIEDNamespaceAccessError
3005NAMESPACE_READ_ONLYNamespaceError*
4001COLLECTION_NOT_FOUNDCollectionNotFoundError
4002COLLECTION_ALREADY_EXISTSCollectionExistsError
4003COLLECTION_INVALID_CONFIGCollectionConfigError
4004COLLECTION_FROZENCollectionError*
5001QUERY_INVALIDQueryError*
5002QUERY_TIMEOUTQueryTimeoutError
5003QUERY_CANCELLEDQueryError*
5004QUERY_TOO_LARGEQueryError*
6001INVALID_VECTOR_DIMENSIONDimensionMismatchError
6002INVALID_METADATAInvalidMetadataError
6003INVALID_IDValidationError*
6004INVALID_FILTERValidationError*
6005MISSING_REQUIRED_FIELDValidationError*
7001NOT_FOUNDSochDBError*
7002ALREADY_EXISTSSochDBError*
7003QUOTA_EXCEEDEDSochDBError*
7004RESOURCE_EXHAUSTEDSochDBError*
8001UNAUTHORIZEDSochDBError*
8002FORBIDDENSochDBError*
8003TOKEN_EXPIREDSochDBError*
8004SCOPE_VIOLATIONScopeViolationError
9001INTERNAL_ERRORSochDBError / DatabaseError
9002NOT_IMPLEMENTEDEmbeddingError
9003STORAGE_ERRORSochDBError*
9004FFI_ERRORSochDBError*
10001DATABASE_LOCKEDDatabaseLockedError
10002LOCK_TIMEOUTLockTimeoutError
10003EPOCH_MISMATCHEpochMismatchError
10004SPLIT_BRAINSplitBrainError
10005STALE_LOCKLockError*
* = generic mapping

Codes marked * are defined in the enum, but from_rust_error() does not have a dedicated subclass for them — they surface as the nearest base class (for example ValidationError or SochDBError) carrying the specific code. Only the rows without * have an entry in the internal _ERROR_MAP. Always read e.code for the exact category rather than relying solely on the exception type.

Exception hierarchy

SochDBError
├── ConnectionError (1001)
├── TransactionError (2001)
│ └── TransactionConflictError (2002)
├── ProtocolError (1004)
├── DatabaseError (9001)
├── NamespaceError
│ ├── NamespaceNotFoundError (3001)
│ ├── NamespaceExistsError (3002)
│ └── NamespaceAccessError (3004)
├── CollectionError
│ ├── CollectionNotFoundError (4001)
│ ├── CollectionExistsError (4002)
│ └── CollectionConfigError (4003)
├── ValidationError
│ ├── DimensionMismatchError (6001) -> .expected, .actual
│ ├── InvalidMetadataError (6002)
│ └── ScopeViolationError (8004)
├── QueryError
│ └── QueryTimeoutError (5002) -> .context["timeout_seconds"]
├── EmbeddingError (9002)
└── LockError (10001)
├── DatabaseLockedError (10001) -> .context["path"], ["holder_pid"]
├── LockTimeoutError (10002) -> .context["path"], ["timeout_secs"]
├── EpochMismatchError (10003) -> .context["expected"], ["actual"]
└── SplitBrainError (10004)

Several classes attach structured context you can read directly:

from sochdb import DimensionMismatchError, DatabaseLockedError

try:
collection.insert(vector, metadata={})
except DimensionMismatchError as e:
print(e.context["expected"], e.context["actual"]) # e.g. 384 128
print(e.code) # ErrorCode.INVALID_VECTOR_DIMENSION

try:
db = sochdb.Database.open("./data")
except DatabaseLockedError as e:
print(e.context["path"], e.context.get("holder_pid"))

NamespaceError and CollectionError also expose convenience properties (e.namespace, e.collection) that read from .context.

Transaction conflict (the one you will retry)

Transaction.commit() raises TransactionConflictError (code 2002) on a Serializable Snapshot Isolation conflict, which corresponds to FFI code -2. This is the canonical retry case:

from sochdb import TransactionConflictError

for attempt in range(5):
try:
with db.transaction() as txn:
txn.put(b"counter", str(value).encode())
break
except TransactionConflictError:
if attempt == 4:
raise
# back off and retry

Mapping engine codes

from_rust_error(code, message, context=None) -> SochDBError is the helper the FFI bindings use to turn an engine error code into the correct typed exception. You can call it yourself if you receive a raw (code, message) pair:

from sochdb import from_rust_error

err = from_rust_error(10001, "locked", {"path": "./data", "holder_pid": 4321})
isinstance(err, sochdb.DatabaseLockedError) # True

Node.js SDK (0.5.3)

The Node.js SDK defines a subset of the taxonomy. Every error extends SochDBError, which carries .code (an ErrorCode enum value), .message, and an optional .remediation string. Catch by instanceof or branch on .code.

import {
SochDBError,
ErrorCode,
TransactionError,
DatabaseLockedError,
} from '@sochdb/sochdb';

try {
await txn.commit();
} catch (err) {
if (err instanceof TransactionError) {
// SSI conflict surfaces here (FFI error_code === -2)
console.error(err.code); // ErrorCode.TRANSACTION_ABORTED (2001)
retry();
} else if (err instanceof DatabaseLockedError) {
console.error(err.path, err.holderPid);
} else if (err instanceof SochDBError) {
console.error(err.code, err.remediation);
}
}

ErrorCode enum (Node.js)

These are the codes actually declared in src/errors.ts:

CodeNameClass
1001CONNECTION_FAILEDConnectionError
1002CONNECTION_TIMEOUT(code only)
1003CONNECTION_CLOSED(code only)
1004PROTOCOL_ERRORProtocolError
2001TRANSACTION_ABORTEDTransactionError
2002TRANSACTION_CONFLICT(code only)
9001INTERNAL_ERRORSochDBError (default)
9003STORAGE_ERRORDatabaseError
10001DATABASE_LOCKEDDatabaseLockedError
10002LOCK_TIMEOUTLockTimeoutError
10003EPOCH_MISMATCHEpochMismatchError
10004SPLIT_BRAINSplitBrainError
10005STALE_LOCK(code only)
Fewer codes than Python

The Node.js enum does not include the 3xxx, 4xxx, 5xxx, 6xxx, 7xxx, or 8xxx ranges. Namespace and collection failures are surfaced through dedicated classes (NamespaceNotFoundError, NamespaceExistsError, CollectionNotFoundError, CollectionExistsError, all extending SochDBError) rather than numbered codes. Do not assume a numeric code exists in Node.js just because Python defines it.

Class hierarchy (Node.js)

Error
└── SochDBError (code, remediation)
├── ConnectionError (1001)
├── TransactionError (2001)
├── ProtocolError (1004)
├── DatabaseError (9003)
├── LockError (10001)
│ ├── DatabaseLockedError (10001) -> .path, .holderPid
│ ├── LockTimeoutError (10002) -> .path, .timeoutSecs
│ ├── EpochMismatchError (10003) -> .expected, .actual
│ └── SplitBrainError (10004)
├── NamespaceNotFoundError (from namespace.ts)
├── NamespaceExistsError
├── CollectionNotFoundError
└── CollectionExistsError

StudioClient throws StudioAPIError(message, statusCode?) and the MCP layer throws McpError(message, code, data?); both extend Error (with MCP_ERROR_CODES exported for the MCP JSON-RPC code set) rather than SochDBError.

Go SDK (0.4.5)

The Go SDK follows Go idioms: sentinel errors for errors.Is() matching, plus error structs for the cases that carry structured fields. The lock-family structs implement Is() so they match their corresponding sentinel.

import (
"errors"
"github.com/sochdb/sochdb-go"
)

// Sentinel match (works through wrapping and through struct Is())
if errors.Is(err, sochdb.ErrDatabaseLocked) {
// back off and retry, or surface to the operator
}

// Typed match when you need the fields
var locked *sochdb.DatabaseLockedError
if errors.As(err, &locked) {
log.Printf("locked at %s by pid %d", locked.Path, locked.HolderPID)
}

Sentinel errors

Defined as package-level vars in errors.go:

SentinelMessage
ErrClosedconnection closed
ErrNotFoundkey not found
ErrInvalidResponseinvalid server response
ErrDatabaseLockeddatabase locked by another process
ErrLockTimeouttimed out waiting for database lock
ErrEpochMismatchepoch mismatch: stale writer detected
ErrSplitBrainsplit-brain: multiple active writers

Error structs

StructFieldsIs() sentinel
ConnectionErrorAddress, Err (has Unwrap())
ProtocolErrorMessage
ServerErrorMessage
TransactionErrorMessage
SochDBErrorOp, Message
LockErrorPath, Message, Remediation
DatabaseLockedErrorPath, HolderPIDErrDatabaseLocked
LockTimeoutErrorPath, TimeoutSecsErrLockTimeout
EpochMismatchErrorExpected, Actual (uint64)ErrEpochMismatch
SplitBrainErrorMessageErrSplitBrain
NamespaceNotFoundErrorNamespace
NamespaceExistsErrorNamespace
CollectionNotFoundErrorCollection
CollectionExistsErrorCollection
FormatConversionErrorFromFormat, ToFormat, Reason
No numeric codes in Go

The Go SDK has no ErrorCode enum — there are no 1xxx/10xxx integers to switch on. Match with errors.Is() (sentinels) or errors.As() (structs). The lock structs embed remediation in LockError.Remediation / the error message text.

Transaction conflict (Go)

In the embedded engine (built with -tags sochdb_embedded), a Serializable Snapshot Isolation conflict surfaces from Transaction.Commit() as a *TransactionError whose message is SSI conflict: transaction aborted due to serialization failure (FFI code -2). Commit() returns error only — there is no commit timestamp in the Go return:

err := db.WithTransaction(func(txn *embedded.Transaction) error {
return txn.Put([]byte("counter"), []byte("1"))
})
var txErr *sochdb.TransactionError
if errors.As(err, &txErr) {
// retry on serialization failure
}

Lock and concurrency errors (all SDKs)

The 10xxx lock family is the most operationally important set: it tells you another process is contending for the same embedded database directory. It exists in all three SDKs with matching semantics.

ConditionPythonNode.jsGoWhat it means
Locked by another processDatabaseLockedError (10001)DatabaseLockedError (10001)ErrDatabaseLocked / DatabaseLockedErrorAnother process already holds the single-writer lock
Lock acquisition timed outLockTimeoutError (10002)LockTimeoutError (10002)ErrLockTimeout / LockTimeoutErrorWaited too long for the lock; possible deadlock
WAL epoch mismatchEpochMismatchError (10003)EpochMismatchError (10003)ErrEpochMismatch / EpochMismatchErrorA newer writer took over; your handle is stale
Split-brainSplitBrainError (10004)SplitBrainError (10004)ErrSplitBrain / SplitBrainErrorMultiple active writers detected; data integrity risk
Stale lockcode 10005 (STALE_LOCK)code 10005 (STALE_LOCK)A leftover lock from a crashed process
Use concurrent open to avoid lock contention

If you need multiple readers alongside a writer in the same process, open with the concurrent path instead of fighting the single-writer lock: Database.open_concurrent(path) (Python), EmbeddedDatabase.openConcurrent(path) (Node.js), or embedded.OpenConcurrent(path) (Go). See the language guides for details: /docs/api-reference/python-api.

Cross-SDK comparison

CapabilityPython (0.5.9)Node.js (0.5.3)Go (0.4.5)
Numeric ErrorCode enumFull (1xxx10xxx)Subset (1xxx, 2xxx, 9xxx, 10xxx)None
Base typeSochDBError(Exception)class SochDBError extends ErrorSochDBError struct + sentinels
Remediation field.remediation + .context.remediationLockError.Remediation / message text
Conflict-detection classTransactionConflictErrorTransactionError*TransactionError
Matching idiomexcept SubclassError / check .codeinstanceof / check .codeerrors.Is / errors.As
Serialize to dict/JSONe.to_dict()read .code, .message, .remediationformat .Error()