Error reference¶
Every message Nautilus can put in front of you, quoted as the source emits it, with what it means, why it happened, and what to do. Search this section for the literal text you saw.
The three surfaces¶
| Surface | Shape | Example |
|---|---|---|
| REST / admin console | JSON body {"detail": "..."} with an HTTP status |
401 {"detail":"Invalid API key"} |
| MCP | tool error carrying the same sentence (ValueError in nautilus/transport/mcp_server.py) |
This credential is bound to agent_id='analyst', so it cannot ask as 'other' |
| CLI | ERROR:, WARN:, FAIL: on stderr (nautilus/cli/_common.py:91-101) |
ERROR: config path does not exist or is not a file: /nope.yaml |
| Python API | an exception from nautilus.core, nautilus.config, nautilus.adapters, nautilus.rkm |
ConfigError: Duplicate source id='s1' |
CLI exit codes (nautilus/cli/_common.py:5-7): 0 success, 1 user error, 2
validation/policy failure. Code 3 is deliberately never used.
Route-by-route and flag-by-flag context lives in the REST API reference and the CLI reference; this section is the string index into both.
Reading a quoted message¶
Headings on these pages are the exact string in the source. Where the source uses an f-string, the heading shows the template and the entry says what interpolates:
{name}— a value substituted withstr().{name!r}— substituted withrepr(), so a string arrives quoted:rule 'my_rule' not found.{exc}— the text of a wrapped, lower-level exception. The sentence before the colon is Nautilus; the sentence after it is the driver, the OS, or a parser.{install_extra_hint(extra)}— the shared remedy clause every "missing optional dependency" message ends with (nautilus/extras.py:31-36). Forextra='postgres'it expands to:
host: pip install 'nautilus-rkm[postgres]'; image: docker build --build-arg EXTRAS="--extra postgres" . (the published image installs --extra otel only, and has no shell or pip to add to it)
Two routes because Nautilus has two runtimes and they take opposite remedies. On a host
install, run the host: half. In the published container image, you cannot: its runtime
stage is distroless — no shell, no pip — so the image: half, a rebuild, is the only
one that works. Which extras that image already carries is
a table in the deployment guide,
held against the Dockerfile and against a built image by
tests/defects/test_wave_ops14_image_extras.py.
A scratch broker¶
Every curl on these pages runs against this. It needs no database and no driver. It is the
REST API reference's scratch broker narrowed until it
fails: keys bound to an agent and a narrow capability list, and api.max_request_bytes at 4096 so
the body limit is reachable by hand. It binds 127.0.0.1:8001, not the 127.0.0.1:8000 the API
reference uses, so both can run side by side.
mkdir -p /tmp/nautilus-errors && cd /tmp/nautilus-errors
cat > nautilus.yaml <<'YAML'
sources:
- id: notes
type: static
classification: unclassified
data_types: [note]
allowed_purposes: [research]
rows:
- {id: 1, body: "hello"}
agents:
analyst:
id: analyst
clearance: unclassified
compartments: []
default_purpose: research
audit:
path: ./audit.jsonl
attestation:
enabled: true
api:
max_request_bytes: 4096
keys:
- key: query-key
agent_id: analyst
capabilities: [query]
- key: govern-key
agent_id: reviewer
capabilities: [query, govern, audit_read, keys]
YAML
Every curl on every page of this section writes its base URL as $NAUTILUS. Export it once per
shell — nothing else on these pages needs a placeholder filled in:
Confirm the broker is up before using the curl examples. {"status":"ok"} means go; a
{"status":"not_ready", …} body is explained in
transport.md:
Find your message¶
Credentials, capabilities, identity — auth.md¶
| Message | Status |
|---|---|
Not authenticated |
401 |
API key required |
401 |
Invalid API key |
401 |
This credential does not hold the {capability!r} capability (it holds {sorted(held)}) |
403 |
This credential is bound to agent_id={bound!r}, so it cannot ask as {body.agent_id!r} |
403 |
This credential is bound to agent_id={bound!r}, so it cannot mint a session token for {requested_agent!r} |
403 |
Forwarded identity rejected: peer is not a trusted proxy |
401 |
Missing X-Forwarded-User |
401 |
X-Nautilus-Reviewer header required |
400 |
api.keys is empty, so every data and governance route will answer 401 Not authenticated. … |
log warning |
api.keys[0] is a bare string: bound to no agent_id, … |
log warning |
api.auth.mode 'proxy_trust' requires api.auth.trusted_proxies. … |
startup |
api.auth.trusted_proxies entry {entry!r} is not an address or CIDR block: {exc} |
startup |
api.keys entry declares unknown capabilities {unknown}. Known capabilities: {list(CAPABILITIES)} |
startup |
Session tokens — session-tokens.md¶
| Message | Reason code |
|---|---|
Invalid session token: {exc.reason_code} |
(401 wrapper) |
Invalid session token ({exc.reason_code}): {exc} |
(401 wrapper) |
No token provided |
missing |
session_token must be a string |
missing |
Cannot decode token header |
bad_signature |
Invalid signature |
bad_signature |
Token decode failed |
bad_signature |
Token header missing kid |
unknown_kid |
Unknown kid: {kid!r} |
unknown_kid |
Key {kid!r} has been revoked |
unknown_kid |
Token has expired |
expired |
Token issued for {broker_instance_id!r}, not {self._broker_instance_id!r} |
broker_instance_mismatch |
session token was minted for agent {claims.agent_id!r}, presented by {agent_id!r} |
agent_mismatch |
session tokens are disabled (session_tokens.enabled: false) |
— |
purpose {purpose!r} is not one of the purposes agent {agent_id!r} may claim ({sorted(record.allowed_purposes)}) |
— |
Unknown agent id='{agent_id}' |
— |
Sessions, exposure ledger, session store — sessions.md¶
| Message |
|---|
session_not_yours: session {state.session_id!r} belongs to another principal. … |
Broker busy: waited {budget}s to take the exposure ledger on {what!r} and did not get it. … |
PostgresSessionStore unavailable (dsn={self._sanitized_dsn()}): {exc} |
PostgresSessionStore unavailable (dsn=…) and sqlite fallback at {self._sqlite_path} failed: {sqlite_exc} |
PostgresSessionStore.aget() called before setup() succeeded |
PostgresSessionStore.aupdate() called before setup() succeeded |
SqliteSessionStore({self._path}) used before setup() succeeded |
session-store pool exhausted: no connection became free within {…}s … |
session-store lock pool exhausted: no connection became free within {…}s … |
session database carries schema version {…}; this build understands version {_SCHEMA_VERSION}. … |
session store at {…} now carries schema version {…}; … |
session database {self._path} carries schema version {found}; … |
session database {self._path} now carries schema version {found}; … |
session_store.backend: redis has no implementation. … |
session_store.backend=postgres requires 'dsn' or TEST_PG_DSN env var |
session_store.backend=postgres needs the 'postgres' extra, whose driver asyncpg is not installed -- {install_extra_hint('postgres')}. Or set session_store.backend to sqlite (durable, single-node) or memory, neither of which needs a driver. |
ERROR: pass exactly one of --sqlite-path or --dsn |
ERROR: no such file: {path} |
ERROR: asyncpg is not installed -- {install_extra_hint('postgres')} |
ERROR: could not connect: {exc} |
ERROR: could not read nautilus_schema_version: {exc} |
ERROR: nautilus_schema_version holds no row |
HTTP transport — transport.md¶
| Message | Status |
|---|---|
Broker not ready |
503 |
Request body is {declared} bytes; this broker accepts at most {limit} (api.max_request_bytes). |
413 |
request body exceeded api.max_request_bytes ({self.max_bytes} bytes) |
connection abort |
Broker busy: {limit} requests are already in flight (api.max_concurrent_requests). Retry. |
503 |
Nautilus could not record this request and will not serve what it cannot account for: {exc} |
503 |
context['scope_constraints'] entry is not a scope constraint: {reasons} |
400 |
invalid datetime: {value!r} |
400 |
Adapter '{name}' not found |
404 |
Adapter '{name}' does not support schema introspection |
501 |
Schema fetch failed: {exc} |
503 |
audit entry {request_id!r} not found |
404 |
{"status": "not_ready", "reason": "startup_incomplete"} |
503 |
{"status": "not_ready", "reason": "<audit probe text>"} |
503 |
{"status": "not_ready", "reason": "session_store_timeout"} |
503 |
{"status": "not_ready", "reason": "{type(exc).__name__}"} |
503 |
Not Found |
404 |
Configuration and startup — config.md¶
| Message |
|---|
Unable to read config file '{config_path}': {exc} |
Invalid YAML in '{config_path}': {exc} |
Config root must be a mapping, got {type(raw).__name__} |
Interpolated config root must remain a mapping |
Config must define a 'sources' list |
Each source entry must be a mapping |
Each source entry must have a string 'id' |
Duplicate source id='{source_id}' |
Source id='{source_id}' is missing the required key 'type' (one of: {sorted(supported_types)}) |
Unsupported source type='{source_type}' for id='{source_id}' (supported: {sorted(supported_types)}) |
Missing env var '{var}' referenced by source id='{source_id}' |
Config validation failed: |
sources.{i}.id: String should match pattern '^[A-Za-z0-9][A-Za-z0-9._-]*$' [type=string_pattern_mismatch] |
source '{self.id}' has type '{self.type}' but no '{required}'. … |
source '{self.id}' has type '{self.type}' but no 'connection'. … |
source id='{source.id}' has type '{source.type}', whose driver is not installed -- {install_extra_hint(extra)} (import failed: {…}) |
adapters[{i}]: module_path does not exist or is not a file: {module_path} |
adapters[{i}]: cannot import module from {module_path} |
adapters[{i}]: error executing {module_path}: {exc} |
adapters[{i}]: class '{cfg.class_name}' not found in {module_path} |
adapters[{i}]: '{cfg.class_name}' in {module_path} is not a class |
adapters[{i}]: '{cfg.class_name}' in {module_path} does not implement the Adapter protocol (missing: {gaps}) |
adapters[{i}]: declared source_type='{cfg.source_type}' does not match {cfg.class_name}.source_type={actual_type!r} in {module_path} |
classification labels are not levels of the 'classification' hierarchy ({known}): … |
analysis.mode={analysis.mode!r} requires analysis.provider to be set |
rkm.auto_promote.enabled: auto-promotion is not implemented. … |
Escalation pack '{yaml_path}' must contain a top-level list, got {type(raw).__name__} |
Each escalation entry in '{yaml_path}' must be a mapping |
Adapters and data sources — adapters.md¶
| Message |
|---|
Operator '{op}' not in allowlist: {sorted(_OPERATOR_ALLOWLIST)} |
Invalid field identifier '{f}' |
table name {table!r} has more than one schema qualifier |
Operator '{op}' requires a list value, got {type(bad).__name__} |
Operator 'IN' requires a list value, got {type(value).__name__} |
Operator 'NOT IN' requires a list value, got {type(value).__name__} |
Operator 'LIKE' requires a string value, got {type(bad).__name__} |
Operator 'BETWEEN' requires a 2-tuple/list value |
operator not allowed: {op} |
{Adapter}.execute called before connect() |
{type(self).__name__}: execute failed for source '{source_id}': {type(exc).__name__}[: {exc}] |
exceeded the source's timeout_s budget of {timeout_s}s |
source '{source_id}' declares mTLS but its client certificate could not be loaded … |
RestAdapter refuses base_url host '{host}': it resolves to private/loopback/link-local address {ip} |
LLMAdapter refuses base_url host '{host}': it resolves to link-local/multicast/unspecified address {ip} |
{adapter} requires a non-empty host in base_url (scheme={scheme!r}; the value is withheld …) |
Refused redirect from host '{base_host}' to different host '{target_host}' (status={…}) |
Refused same-host redirect (status={…}); configure the endpoint path directly … |
source '{source_id}' answered with {declared} bytes, over the {MAX_RESPONSE_BYTES}-byte ceiling |
source '{source_id}' object {key!r} is {declared} bytes, over the {MAX_OBJECT_BYTES}-byte ceiling |
ElasticsearchAdapter: field '{field}' is mapped as analysed 'text' with no 'keyword' subfield, … |
ElasticsearchAdapter: the only exact subfield for '{field}' is '{field}.{chosen}', which has ignore_above={limit}, … |
NoopEmbedder(strict=True) cannot produce embeddings. … |
context['embedding'] must be list[float], got {type(override).__name__} |
sn-invalid-field: {field!r}, sn-injection-rejected, sn-unsupported-operator: {op!r} |
source type '{source_type}' needs its driver -- {install_extra_hint(extra)} |
the 'llm-anthropic' extra is not installed -- {install_extra_hint('llm-anthropic')} |
AnthropicProvider: env var {self.api_key_env!r} is unset or empty |
PostgresAdapter requires 'table' on source '{config.id}' (Phase 1 shortcut) |
PostgresAdapter missing 'table' for source '{self._config.id}' |
PgVectorAdapter requires 'table' on source '{config.id}' |
PgVectorAdapter missing 'table' for source '{config.id}' |
distance_operator '{distance_operator}' not in allowlist: {sorted(_ALLOWED_DISTANCE_OPERATORS)} |
ElasticsearchAdapter requires non-empty 'index' on SourceConfig |
Invalid Elasticsearch index '{index}': must match {_INDEX_PATTERN.pattern} |
ElasticsearchAdapter could not read the mapping for index '{self._index}': {exc} |
Neo4jAdapter requires non-empty 'label' on SourceConfig |
Invalid Neo4j label '{label}': must match {_LABEL_PATTERN.pattern} |
Invalid Neo4j property identifier '{name}' |
neo4j: get_schema failed for source '{self._config.id}': {exc} |
InfluxDBAdapter: '_time' bound {value!r} is not a Flux duration … |
InfluxDBAdapter: '_time' bound {value!r} is not a time, duration or Unix-second integer |
InfluxDBAdapter: operator {op!r} is not expressible as a time range on '_time' … |
InfluxDBAdapter: LIKE pattern {pattern!r} uses the '_' single-character wildcard, … |
InfluxDBAdapter: LIKE pattern {pattern!r} has an interior '%' wildcard, … |
InfluxDBAdapter: source '{config.id}' declares auth type {auth.type!r}, which InfluxDB cannot use. … |
influxdb: get_schema failed for source '{self._config.id}': {exc} |
S3Adapter: source '{config.id}' declares auth type {auth.type!r}, which S3 cannot use. … |
S3Adapter: unsupported operator '{op}' for field 'key' |
S3Adapter: unsupported operator '{op}' for tag filter |
S3Adapter: unsupported operator '{op}' for classification |
S3Adapter: unsupported scope field '{field}' |
S3Adapter: empty tag name |
S3Adapter: IN operator requires a list value, got {type(value).__name__} |
S3Adapter: LIKE operator requires a string value |
S3Adapter request failed for source '{self._config.id}': {exc} |
sn-invalid-value: operator {op!r} requires a list, got {type(cast(object, value)).__name__} |
sn-invalid-value: operator 'BETWEEN' requires a 2-tuple/list |
sn-invalid-value: operator 'BETWEEN' requires exactly two endpoints |
ServiceNowAdapter source '{config.id}' has invalid table {table!r} (expected regex '^[a-z][a-z0-9_]*$') |
sn-attachment-fetch-cap: {len(rows)} rows pinned, cap is {_MAX_ATTACHMENT_FETCHES} |
servicenow: get_schema failed for source '{self._config.id}': {exc} |
Operator 'NOT IN' is not supported by the REST adapter unless explicitly declared … |
RestAdapter source '{config.id}' declares endpoints=[] (must list at least one EndpointSpec …) |
EndpointSpec.operator_templates declares unknown operator '{op}' for source '{config.id}' |
LLMAdapter source '{config.id}' requires a 'model' field in its source block |
LLMAdapter source '{config.id}' does not support mTLS auth; use bearer/basic … |
LLMAdapter call failed: {exc} |
LLMAdapter received a non-OpenAI-compatible response shape: {exc} |
| …and the static adapter's operator refusal, plus the analysis-provider messages below. |
Rules, RKM and the policy engine — rules.md¶
| Message |
|---|
ERROR {serr.file}:{serr.line}: {serr.message}{hint_suffix} |
proposed rule {rule_name!r} does not compile: {exc} |
Regression detected: rule {rule_name!r} denies access the current rules grant, in {regression_count} of {replayed} replayed request(s). First: {first_regression} |
proposal not found: {proposal_id!r} |
{"error": "already_decided", "current_status": …} |
cannot transition {proposal_id} from {current!r} to {to!r} |
lock contention timeout on proposal queue |
proposal {proposal.proposal_id!r} carries no rule YAML: artifact has neither 'yaml' nor 'yaml_path' (keys: {sorted(artifact)}) |
proposal {proposal.proposal_id!r} references unreadable rule file {path!r}: {exc} |
FathomRouter.reload_rule failed for proposal {proposal_id!r}: {exc} |
cannot promote rule {rule_name!r}: no rules.user_rules_dirs is configured, … |
cannot promote rule {rule_name!r}: writing {target} failed: {exc} |
cannot retract rule {rule_name!r}: it is still in force after a rebuild, … |
rule {rule_name!r} not found |
rule {rule_name!r} version {version} not found |
rule {rule_name!r} v{to_version} not found in lineage |
lineage record not found: {rule_name} v{version} |
invalid rule name {rule_name!r}: expected a bare rule name, optionally prefixed 'module::' |
rule pack {pack_name!r} is claimed by more than one installed distribution … |
Fathom engine construction failed: {exc} |
FathomRouter.route() failed for agent_id={agent_id!r}: {exc} |
routing_unknown_source, scope_without_routing, denial_unknown_source, denial_missing_linkage, agent_fact_integrity, session_exposure_count |
rule {rule_name!r} targets routing-owned template {template!r} |
{path}:{approx_line}: invalid relationship_type={rel_type!r}; must be one of [{valid}] |
body must carry 'rule_yaml': the contents of the rule file |
reason is required for rejection, reason is required for retraction, to_version is required for rollback, yes=true required for destructive operation |
Attestation, keys and the chained log — attestation.md¶
| Message |
|---|
kid must be a UUID |
reviewer is required (no control characters) |
reviewer and reason are required (no control characters) |
kid {kid!r} not found |
kid {kid!r} is the current primary; rotate first, then revoke |
Key {entry.kid!r} has no private key (revoked) |
Expected Ed25519PrivateKey / Expected Ed25519PublicKey |
attestation is disabled |
attestation.sink.chained requires attestation.enabled with a signing key |
audit.chained requires attestation.enabled with a signing key: … |
audit.chained cannot append to the existing chain at {audit_path} with an auto-generated signing key: … |
{subject} is already open for writing by {holder}. A hash chain admits exactly one writer: … |
emit on closed ChainedFileAttestationSink |
unreadable offsets file {path}: {exc} |
offsets payload must be a JSON object, got {type(payload).__name__} |
last_byte_offset must be int, got {type(offset_raw).__name__} |
last_byte_offset must be non-negative, got {offset_raw} |
seen_line_sha256 must be list, got {type(seen_raw).__name__} |
seen_line_sha256 entries must be str, got {type(item).__name__} |
refusing non-monotonic save: current={…} < persisted={…} |
AuditRecord has no {NAUTILUS_METADATA_KEY!r} metadata |
Command line — cli.md¶
| Message | Exit |
|---|---|
ERROR: NAUTILUS_REVIEWER env var required for this command. Set it to your operator identity. |
1 |
ERROR: this decision cannot be recorded, so it will not be taken: {problem} |
2 |
ERROR: this acknowledgement cannot be recorded, so it will not be made: {problem} |
2 |
ERROR: config path does not exist or is not a file: {config_path} |
2 |
ERROR: invalid config: {exc} |
2 |
ERROR: broker construction failed: {exc} |
2 |
ERROR: config: no subcommand given (try: check) |
2 |
ERROR: file not found: {file_path} |
1 |
--bind must be HOST:PORT, got {bind!r} |
2 |
--bind port must be an integer, got {port_s!r} |
2 |
Unable to read config '{config_path}': {exc} |
2 |
application startup failed; the server never accepted a connection. The cause is logged above. |
2 |
FAIL {status} {url} / FAIL unreachable {url}: {exc} |
1 |
WARN: --air-gapped drops LLM source id={…!r} — connection host is not loopback (NFR-1, #43) |
— |
ERROR: rkm: no subcommand given (try: queue, lineage) |
2 |
ERROR: rkm queue: no op given (try: submit, list, show, approve, reject, diff) |
2 |
ERROR: rule: no subcommand given (try: list, retract, lineage, history, rollback) |
2 |
ERROR: rules: no subcommand given (try: validate, test, history) |
2 |
ERROR: adapters: no subcommand given (try: new, list, schema, schema-fingerprint, schema-diff, schema-ack) |
2 |
ERROR: key: no subcommand given (try: list, rotate, revoke) |
2 |
ERROR: attestation: no subcommand given (try: verify) |
2 |
ERROR: events: no subcommand given (try: list) |
2 |
ERROR: {target} already exists — refusing to overwrite it |
1 |
FAIL: key {command}: --url is required. … |
2 |
ERROR: rotate requires --yes to confirm. |
1 |
ERROR: revoke requires --yes to confirm. |
1 |
FAIL: key {command}: cannot reach {endpoint}: {exc} |
2 |
ERROR: key {command}: server returned {status_code}: {text} |
2 |
ERROR: --yes required for destructive op |
1 |
ERROR: --reason required for retract |
1 |
ERROR: --cascade and --orphan-children are mutually exclusive |
1 |
ERROR: rule {name!r} not found in lineage |
1 |
ERROR: rule {name!r} v{to_version} not found in lineage |
1 |
WARN: no lineage records for {name!r} / WARN: no history for {name!r} |
0 |
ERROR: rule file not found: {rule_path} |
1 |
ERROR: rkm queue approve: --url is required. … |
2 |
ERROR: rkm queue approve: cannot reach {endpoint}: {exc} |
2 |
ERROR: rkm queue approve: server returned {status_code}: {text} |
2 |
ERROR: proposal {proposal_id} not found |
1 |
ERROR: proposal {proposal_id} cannot be rejected: it is {current_status} |
1 |
WARN: could not read rkm settings from {config_path!r} ({exc}); using defaults |
0 |
WARN: no lineage records for {id!r} |
0 |
ERROR: invalid adapter name {name!r} (expected lowercase-dashed, e.g. my-csv-adapter) |
1 |
ERROR: destination already exists and is not empty: {dest} |
1 |
ERROR: copier is required for 'adapters new' — install it with: pip install copier |
1 |
ERROR: no config found: pass --config PATH, or run from a directory containing nautilus.yaml |
1 |
ERROR: --status {status_filter!r} needs --url: quarantine state lives in the serving process, … |
1 |
ERROR: could not load {config_path}: {exc} |
1 |
ERROR: {url} refused the credential (401). Pass a valid --api-key. |
1 |
ERROR: could not reach {url}: {exc} |
1 |
ERROR: no schema available for adapter {name!r} |
1 |
ERROR: no schema available for adapter {name!r}; cannot ack |
1 |
ERROR: schema-ack requires --yes to confirm |
1 |
WARN: could not read schema for {name!r}: {exc} |
1 from schema, schema-fingerprint and schema-ack; 0 from schema-diff |
WARN: no stored fingerprint for {name!r}; treating as new |
0 |
ERROR: attestation verify: log not found: {log_path} |
1 |
ERROR: attestation verify: pubkey not found: {pubkey_path} |
1 |
ERROR: attestation verify: {result.error} |
2 |
WARN: could not read audit path from {config_path!r} ({exc}); using {path} |
0 |
ERROR: audit log not found: {audit_path} |
1 |
ERROR: cannot read rules config from {path}: {exc} |
1 |
ERROR: score {min_total} below threshold {threshold}: {file_path} |
2 |
WARN: no rules found in {file_path} |
0 |
WARN: affected descendants: {names} |
0 |
WARN: rule '{name}': shadow finding {relation} (existing rule '{existing_rule}') |
0 |
WARN: rule '{name}': insufficient audit history (replayed {replayed_n_actual} entries) |
0 |
WARN: rule '{name}': {skipped_drifted} audit entries could not be replayed -- … |
0 |
WARN: rule '{name}': {skipped_no_input_facts} audit entries carry no engine input and were not replayed |
0 |
Embedding Nautilus as a library — library.md¶
| Message |
|---|
Broker.request() called inside a running event loop. Use Broker.arequest() (async) from async contexts. |
Broker.close() called inside a running event loop. Use Broker.aclose() (async) from async contexts. |
Broker.{method}() called after close(); the attestation sink and session store are already shut down, … |
Broker.declare_handoff() failed for source={source_agent_id!r} receiving={receiving_agent_id!r}: {exc} |
create_app requires either config_path or existing_broker |
create_server requires either config_path or existing_broker |
AC-21.b: this adapter must implement get_schema() (task-006) |
adapter id {adapter_id!r} is not usable as a fingerprint filename |