---
title: Errors & self-repair
description: The catalog of OMGDB's real error messages — typo hints, validation failures, unique conflicts, corruption reports, lock refusals — and how to recover from each.
---

OMGDB treats error messages as part of the agent surface. Every error is deterministic, names the exact offending token or field, and — wherever a typo is plausible — carries a did-you-mean hint computed by edit distance against the real vocabulary. An AI agent (or a human) can read the message and repair the call without consulting anything else. This page catalogs the messages the engine actually produces, grouped by surface, with the exact strings.

On the CLI these arrive on stderr with a non-zero exit; over [MCP](/docs/mcp/) the same strings come back as tool results flagged `isError: true`.

## Unknown operators

The engine never answers a misspelled operator with a generic parse failure. Each of the three operator vocabularies — query, update, aggregation — rejects unknown tokens by name, with a suggestion when a known operator is within edit distance 2.

A typo in a **query filter**:

```text
unknown query operator: $gtee (did you mean `$gte`?)
```

A typo in an **update document** (`plan-update`, MCP `plan_update`):

```text
unknown update operator: $sett (did you mean `$set`?)
```

Typos in an **aggregation pipeline** are qualified by where they occurred — a stage, an accumulator, or an expression:

```text
unknown aggregation operator: $sortt (stage) (did you mean `$sort`?)
unknown aggregation operator: $frist (accumulator) (did you mean `$first`?)
unknown aggregation operator: $toUppr (did you mean `$toUpper`?)
```

The same treatment extends into operator arguments. `$type` checks its type names, and it refuses names that would silently match nothing — OMGDB stores integers as `long`, so `int` is redirected instead of compiled into an always-false predicate:

```text
malformed query: $type: unknown type name `strin` (did you mean `string`?)
malformed query: $type: OMGDB stores no `int` values (integers are `long`); use `long`, `double`, or `number`
```

If no known token is close enough, the error simply names the bad operator (for example `unknown update operator: $bogus`).

## Missing collections

A typo'd collection name must never produce confidently wrong output, so the two introspection commands that give advice — `explain` and `diagnose` — fail loudly, listing what does exist:

```text
collection `ticket` does not exist — did you mean `tickets`? (existing: tickets, users)
```

On an empty store the variant is:

```text
collection `ticket` does not exist (the store has no collections yet)
```

`find` is different by design: it keeps MongoDB's semantics, where querying a missing collection returns an empty result and exits zero. But an agent should still hear about the likely typo, so the hint goes to stderr as a warning while stdout stays empty:

```text
warning: collection `ticket` does not exist — did you mean `tickets`?
```

## Validation errors

Writes into a collection with a [validation spec](/docs/schema-validation/) are checked before anything is appended. Every failure is wrapped as `validation failed for `<collection>`: <reason>`.

A required field is absent:

```text
validation failed for `users`: missing required field `name`
```

A field has the wrong type (`number` accepts both integers and doubles; every other name is exact):

```text
validation failed for `users`: field `age` should be `long` but is `string`
```

A declared reference points at a document that does not exist:

```text
validation failed for `posts`: field `author` references missing `users._id` {"$oid":"018f2f4c9d1e4a0b8c000001"}
```

The spec itself is validated **at define time**, not at first insert. A spec with an unknown field type would reject every future write, so `define` fails immediately and lists the entire accepted vocabulary:

```text
validation failed for `users`: field `age` has unknown type `integer` (expected one of: array, binData, bool, date, double, long, markdown, null, number, object, objectId, string)
```

## Unique and `_id` conflicts

Inserting a document whose `_id` already exists in the collection (the `_id` is echoed in canonical JSON):

```text
duplicate _id in `users`: {"$oid":"018f2f4c9d1e4a0b8c000001"}
```

Violating a unique index reports the collection, the indexed field, and the conflicting value:

```text
unique index violation on `users.email` for value "ada@example.com"
```

For a compound unique index the fields are comma-joined and the value is the conflicting tuple:

```text
unique index violation on `users.org,email` for value ["acme","ada@example.com"]
```

The same check runs when the index is *created*: a unique index over existing data that already contains duplicates fails with this error rather than building a broken index.

## Corruption, CRC, and repair

Every op-log record carries a CRC-32 and a dense LSN, verified on every read. The default open path is fail-stop: corruption is reported, never silently skipped.

```text
log corruption: line 3: CRC mismatch
log corruption: line 3: lsn 4 (expected 2)
log corruption: a complete record contains invalid UTF-8
```

> **Note:** A *torn tail* is not corruption. If a write was interrupted mid-append (a crash, a kill), the incomplete trailing fragment is preserved to `oplog.torn.bak` and the log is trimmed to its durable prefix automatically on the next open — no data that was ever acknowledged is touched. Corruption means a *complete* record fails its checks.

Recovery from genuine corruption is opt-in, through `omgdb repair`. With no flags it is a non-destructive dry run:

```sh
omgdb repair app.omgdb
```

```text
CORRUPT at byte 412 of 897: CRC mismatch
recoverable prefix: 3 record(s)
re-run with `--truncate --yes` to drop the corrupt tail (a backup is kept)
```

The recoverable prefix always ends on a record boundary. To actually truncate to it, both flags are required (`--truncate` alone refuses with ``refusing to modify the log without `--yes` ``), and the original file — including the corrupt tail — is backed up first:

```sh
omgdb repair app.omgdb --truncate --yes
```

```text
CORRUPT at byte 412 of 897: CRC mismatch
recoverable prefix: 3 record(s)
repaired: kept 3 record(s); corrupt original backed up to app.omgdb/oplog.ndjson.corrupt.bak
```

On an intact log, `repair` says so and does nothing:

```text
OK: log is intact (12 record(s)); nothing to repair
```

Run `omgdb verify app.omgdb` afterwards — it re-proves that the log reproduces the full state, including every derived cache. See [storage](/docs/storage/) for the durability model behind all of this.

## Store lock

A store is single-process: opening it takes an exclusive advisory lock, and a second process is refused cleanly rather than corrupting anything:

```text
store at `app.omgdb` is locked by another process
```

`repair` checks the same lock before touching the raw log, with its own guidance:

```text
store at `app.omgdb` is locked by another process; close it before repairing
```

Recovery is simply to close the other process — the lock is released the moment the holding process exits, even if it crashed. If you are running the [MCP server](/docs/mcp/), note that it takes the lock only for the duration of each tool call, so a persistent lock error points at some *other* long-lived holder.

## Related

- [Connect your coding agent](/docs/agents/) — wiring these errors into an agent's repair loop over MCP.
- [Schema validation](/docs/schema-validation/) — the spec vocabulary behind the validation errors.
- [Storage & op-log](/docs/storage/) — CRC framing, torn-tail recovery, and the fail-stop open path.
- [CLI reference](/docs/cli/) — the full `repair` and `verify` command documentation.
