Skip to main content

Error Handling

Garak provides structured error responses that help you handle failures gracefully and debug issues effectively.

Error Response Structure

All errors from Garak follow this format:

{
"field": "fieldName",
"code": "ERROR_CODE",
"message": "Human readable error message",
"kind": "KIND_CONSTANT"
}

Properties

  • field: The field that caused the error (empty string if not applicable)
  • code: A unique error code for programmatic handling
  • message: Human-readable description of the error
  • kind: Category of error for broad classification

Error Kinds

KIND_OPERATION_INCOMPATIBLE_WITH_FIELD

The operation cannot be performed on the field due to type mismatch.

Common Scenarios:

  • Trying to increment a non-numeric field
  • Using addToSet or pull on a non-array field

Example:

// Document: { name: "Alice", age: 25 }

// This will fail:
await tx.inc(store, nodeId, "name", 1);
// Error: field "name" is not a number

Error Codes:

  • ETARGETFIELDNOTINT - Field is not a number
  • ETARGETFIELDNOTARR - Field is not an array

Handling:

try {
await tx.inc(store, nodeId, "field", 1);
} catch (error) {
if (error.kind === "KIND_OPERATION_INCOMPATIBLE_WITH_FIELD") {
if (error.code === "ETARGETFIELDNOTINT") {
console.error("Cannot increment non-numeric field");
} else if (error.code === "ETARGETFIELDNOTARR") {
console.error("Cannot add to non-array field");
}
}
}

KIND_NODE_NOT_FOUND

The requested node does not exist.

Common Scenarios:

  • Querying non-existent node_id
  • Node was deleted
  • Wrong shard specified

Example:

const node = await tx.get(store, 999999);
// Error: Node 999999 not found in silo/shard

Error Code:

  • ENOTFOUND - Node not found

HTTP Status: 404

Handling:

try {
const node = await tx.get(store, nodeId);
} catch (error) {
if (error.kind === "KIND_NODE_NOT_FOUND") {
console.log("Node does not exist");
// Handle missing node (create new, use default, etc.)
}
}

KIND_SCHEMA_VIOLATION

The operation violates index constraints.

Common Scenarios:

  • Inserting duplicate value in secondary key index
  • Updating field to duplicate secondary key value

Example:

const emailIndex = new Garak.SecondaryKeyIndex(usersStore, "email");

// First insert succeeds
await tx.insert(usersStore, { email: "alice@example.com" });

// Second insert fails
await tx.insert(usersStore, { email: "alice@example.com" });
// Error: Duplicate value in secondary key index

Error Codes:

  • EDUPLICATE - Value violates uniqueness constraint
  • EVIOLATION - General schema violation

Handling:

try {
await tx.insert(store, data);
} catch (error) {
if (error.kind === "KIND_SCHEMA_VIOLATION") {
if (error.code === "EDUPLICATE") {
console.error(`Duplicate value for field: ${error.field}`);
// Handle duplicate (update existing, show error to user, etc.)
}
}
}

KIND_MALFORMED_INDEX

The index is corrupted or misconfigured.

Common Scenarios:

  • Index files corrupted on disk
  • Index definition conflicts
  • Concurrent index modifications

Error Codes:

  • EIDXCORRUPT - Index file corrupted
  • EIDXCONFLICT - Index definition conflict

Handling:

try {
const result = await index.lookup(tx, key);
} catch (error) {
if (error.kind === "KIND_MALFORMED_INDEX") {
console.error("Index corrupted, needs rebuild");
// Trigger reindex or alert operations team
}
}
danger

Malformed index errors usually require manual intervention. Consider triggering a reindex operation.

KIND_OVERSIZE_NODE

The node data exceeds the 25KB limit.

Common Scenarios:

  • Inserting document > 25KB
  • Large embedded objects
  • Long text fields

Example:

const hugeData = {
content: "x".repeat(30_000) // 30KB string
};

await tx.insert(store, hugeData);
// Error: Node data too large (30000 bytes), Garak max size per node is 25kb

Error Code:

  • ERROR_NODE_TOO_LARGE - Node exceeds size limit

Handling:

try {
await tx.insert(store, data);
} catch (error) {
if (error.kind === "KIND_OVERSIZE_NODE") {
console.error("Document too large, consider splitting it");
// Split document, store large fields separately, etc.
}
}

Client Error Handling

TypeScript/Node.js

The TypeScript client provides a DatabaseError class:

import { Garak } from '@your-org/garak-client';

class DatabaseError extends Error {
field: string;
code: string;
db_message: string;
httpStatus: number;
kind: string;
}

// Usage
try {
await tx.insert(store, data);
} catch (error) {
if (error instanceof DatabaseError) {
console.error({
field: error.field,
code: error.code,
message: error.db_message,
httpStatus: error.httpStatus,
kind: error.kind
});
}
}

Rust

The Rust client uses a DatabaseError type:

use garak::{DatabaseError, DatabaseErrorKind};

match client.insert(&store, &data).await {
Ok(result) => println!("Inserted: {}", result.node_id),
Err(e) => match e.kind {
DatabaseErrorKind::KIND_SCHEMA_VIOLATION => {
eprintln!("Schema violation: {}", e.message);
}
DatabaseErrorKind::KIND_OVERSIZE_NODE => {
eprintln!("Node too large: {}", e.message);
}
_ => eprintln!("Error: {}", e.message)
}
}

HTTP Status Codes

Garak uses standard HTTP status codes:

StatusMeaningCommon Causes
200SuccessOperation completed successfully
202AcceptedAsync job started (cold sync)
400Bad RequestInvalid input, type mismatch, size limit
401UnauthorizedMissing Authorization header
403ForbiddenInvalid token or insufficient permissions
404Not FoundNode doesn't exist, job not found
500Internal Server ErrorUnexpected server error

Common Error Patterns

Handling Missing Nodes

async function getOrCreate(
tx: Transaction,
store: Store<User>,
nodeId: number
): Promise<User> {
try {
return await tx.get(store, nodeId);
} catch (error) {
if (error.kind === "KIND_NODE_NOT_FOUND") {
// Create default user
const { node_id } = await tx.insert(store, {
id: nodeId,
name: "Unknown",
createdAt: Date.now()
});
return await tx.get(store, node_id);
}
throw error;
}
}

Handling Duplicate Keys

async function upsertByEmail(
tx: Transaction,
store: Store<User>,
index: SecondaryKeyIndex<User>,
email: string,
data: Partial<User>
): Promise<number> {
// Try to find existing
const { node_id } = await index.lookup(tx, email);

if (node_id !== null) {
// Update existing
for (const [key, value] of Object.entries(data)) {
await tx.set(store, node_id, key, value);
}
return node_id;
}

// Insert new
try {
const result = await tx.insert(store, { email, ...data });
return result.node_id;
} catch (error) {
if (error.kind === "KIND_SCHEMA_VIOLATION" && error.code === "EDUPLICATE") {
// Race condition: another process inserted between lookup and insert
// Retry the operation
return await upsertByEmail(tx, store, index, email, data);
}
throw error;
}
}

Debugging Errors

Enable Verbose Logging

Set environment variable for detailed logs:

export RUST_LOG=debug

Inspect Error Details

Always log the full error object for debugging:

catch (error) {
console.error("Full error details:", {
name: error.name,
message: error.message,
field: error.field,
code: error.code,
kind: error.kind,
httpStatus: error.httpStatus,
stack: error.stack
});
}

Check Server Logs

Garak logs all operations with timing:

-> Request URL: /insert
Inserting data into shard: my-shard, store: users
Generating node_id: currently at 12345
Inserted node_id: 12346 (took 5 ms)
<- Responded to /insert in 5 ms

Best Practices

  1. Always handle KIND_NODE_NOT_FOUND: Nodes can be deleted concurrently
  2. Implement retry logic for EDUPLICATE: Handle race conditions
  3. Validate data size before insert: Avoid KIND_OVERSIZE_NODE at runtime
  4. Type-check before operations: Prevent KIND_OPERATION_INCOMPATIBLE_WITH_FIELD
  5. Log error context: Include node_id, operation, and state for debugging
  6. Monitor error rates: Track error kinds and codes for system health
  7. Fail fast: Don't silently swallow errors; propagate or handle explicitly

Error Recovery

Automatic Recovery

Some errors are automatically recoverable:

  • Network timeouts: Retry with exponential backoff
  • Temporary locks: Retry after brief delay
  • Race conditions: Retry operation with updated state

Manual Recovery

Some errors require manual intervention:

  • KIND_MALFORMED_INDEX: Trigger reindex operation
  • Corrupted data files: Restore from backup
  • Disk full: Free space or expand storage
  • Master lock stuck: Clear lock file manually

Rollback Strategy

Since Garak doesn't have automatic transaction rollback:

async function multiStepOperation(tx: Transaction) {
const createdNodes: number[] = [];

try {
// Step 1
const { node_id: id1 } = await tx.insert(store1, data1);
createdNodes.push(id1);

// Step 2
const { node_id: id2 } = await tx.insert(store2, data2);
createdNodes.push(id2);

// Step 3 fails
await tx.insert(store3, invalidData);

} catch (error) {
// Manual rollback
for (const nodeId of createdNodes) {
await tx.delete(store1, nodeId).catch(() => {});
}
throw error;
}
}