Troubleshooting
Common issues, error messages, and solutions for Mubit SDK and API usage.
Start with mubit doctor
The CLI ships with the Python package (pipx install mubit-sdk). mubit doctor prints the endpoint, the masked key, the server mode, the resolved scope, the health probe and one line per problem; it exits 1 when it found any.
mubit doctor # human-readable
mubit doctor --json # the same fields as JSONendpoint: https://api.mubit.ai
key: mbt_acme_k1_****
mode: loop_v1 (server 0.4.0, policy starter@1)
scope: project=first-project env=dev agent=(unset)
health: ok (/v2/core/health)
deprecations: 0 distinct warnings emitted in this processmode is loop_v1 (full loop), legacy (server without /v2/loop: run-level outcomes are ignored and run end reflects synchronously) or unreachable. mubit status --agent <id> adds events_received, reflections_scheduled/reflections_finished, lessons_stored, last_injection and last_outcome.
Authentication
Invalid API key
Symptom: AuthenticationError (HTTP 401) from Client, or, on the loop helpers, one line:
WARNING mubit: rejected by https://… (HTTP 401 Unauthorized: ACL error: Invalid API key). Memory is off for this process. Check MUBIT_API_KEY, or run `mubit doctor`.The helpers then continue without memory; ContextBlock.reason is unauthorized.
| Cause | Fix |
|---|---|
| Malformed key | Keys follow the format mbt_<instance>_<key_id>_<secret>. Verify all segments are present. |
| Wrong instance | The instance tag in the key must match the endpoint's instance. A local key is accepted only by a local endpoint; a hosted key only by api.mubit.ai. |
| Revoked key | Create a new key in the console under Settings → API keys → your project. |
| Missing env var | Set MUBIT_API_KEY, or pass mubit.init(api_key=...) / Client(api_key=...). |
To fail hard instead of warning, use mubit.init(on_error="raise") or MUBIT_ON_ERROR=raise.
Permission denied
Symptom: PermissionDeniedError (HTTP 403) with a body such as principal manager cannot set the kill switch; needs admin.
Control-plane writes need a principal of the named kind. The key's role sets the principal (Admin key → admin, User key → worker); x-mubit-principal can only lower it. See Loop console → principals.
Transport and connectivity
Connection refused
Symptom: APIConnectionError: connection refused by 127.0.0.1:3000 (gRPC and HTTP). Is Mubit running? MUBIT_ENDPOINT=http://127.0.0.1:3000 from Client, or WARNING mubit: cannot reach http://127.0.0.1:3000 (connection refused). Running without memory; will retry on the next call. Set MUBIT_ENDPOINT if this is the wrong server. from the helpers.
| Context | Fix |
|---|---|
| Local development | Ensure Mubit is running. The SDK fails a refused connection within connect_timeout (2 s) and retries only 429/5xx; the loop helpers continue without memory and print one warning. |
| Hosted endpoint | Check network connectivity to api.mubit.ai. Verify no firewall blocking HTTPS/443. |
| Wrong server | A default of 127.0.0.1:3000 in the message means MUBIT_ENDPOINT is unset for this process (the CLI and learn paths still default to the local address in 0.14). Set it to https://api.mubit.ai or copy the four .env lines from the console. |
| gRPC transport | Verify MUBIT_GRPC_ENDPOINT points to the correct host. |
Transport selection issues
Symptom: Unexpected transport used, or auto-detection fails.
Set MUBIT_TRANSPORT explicitly to "http" or "grpc" instead of relying on "auto". The auto probe waits connect_timeout (2 s) for gRPC readiness before it falls back to HTTP; set MUBIT_CONNECT_TIMEOUT_MS if your network needs longer.
gRPC field names wrong (JS SDK)
Symptom: Response fields use camelCase instead of snake_case proto names, or vice versa.
Fix: Update @mubit-ai/sdk to the latest version. Current SDKs decode proto responses with keepCase: false by default, so fields surface as camelCase. Option objects are camelCase-only in 0.14; a snake_case key is accepted with one console.warn and rejected in 1.0.
The loop
No lesson injected on run two
| Cause | Fix |
|---|---|
Different env | Lessons are partitioned by env. Both runs must use the same MUBIT_ENV (or env= argument). The ContextBlock.scope of run two shows which project/env was used; MUBIT_LOG=info prints context: 0 units for env=prod project=default. |
| Run one had nothing to learn from | A run needs a correction, a tool failure or an outcome. Record one with run.outcome(good=False, label="..."). |
| Reflection not finished | The server distils a few seconds after run.end. mubit status --agent <id> shows reflections_finished and lessons_stored. |
| Run one never ended | See Lost turns. |
| Kill switch on | The SDK prints WARNING mubit: kill switch is on for env=dev (reason: "…"); injection disabled. once; reason is killed. Turn it off in the console (admin) or mubit kill off. |
| Legacy server | WARNING mubit: server at … has no /v2/loop (legacy mode). Upgrade the instance. |
Lessons not appearing after reflect
| Cause | Fix |
|---|---|
| Looking in the wrong list | Under the built-in starter@1 policy, lessons distilled at run end are stored active with scope=session (confidence 0.5) and become visible to other runs in the same env according to the overlay gate (same actor, or promotion, or gate.min_actors distinct actors). Explicit lessons written with remember(kind="lesson") or note(intent="lesson") are gated (gate_notes=true) and enter as pending; mubit lessons list --status pending shows them. |
Gate mode is propose or external | The lesson is waiting for review. Open Proposals in the console or mubit proposals list. |
| Run never ended | A process that exits without ending its run (os._exit, process.exit(), a crash) loses the turns it captured but had not flushed; call mubit.shutdown() or use with mubit.run(): / await mubit.run(fn). |
| Not sure what happened | Run mubit doctor, then mubit status --agent <id> and mubit lessons transitions --run <run_id>. |
Lost turns when the process exits
Symptom:WARNING mubit: 2 captured events were not sent before exit (run run-…); call mubit.shutdown() or end the run inside `with mubit.run():`.The capture worker posts events in the background. with mubit.run() (Python) and await mubit.run(fn) (JS) flush on exit; mubit.shutdown(timeout=2.0) flushes when you manage runs yourself. os._exit() and process.exit() skip both.
OutcomeReceipt(sent=False)
reason | Meaning |
|---|---|
nothing_to_record | No good, score or label was given, or there is no current run |
uninitialized | No mubit.init() and nothing resolvable from env |
disabled | MUBIT_DISABLED=1 or init(disabled=True) |
unreachable, unauthorized, killed, legacy | See the rows above |
Memory and retrieval
Recall returns empty results
| Cause | Fix |
|---|---|
| Data not yet ingested | client.memory.remember() waits for the ingest job by default (wait=True). If you wrote through client.raw.invoke("control.ingest", ...), poll client.jobs.get(job_id, run_id=...) until done is true. |
Wrong run_id | recall reads the current run. Pass run_id= to read another run, or store the item with visibility="global". |
| Entry-type filter too narrow | Broaden entry_types (kinds on mubit.recall()) or omit it to search all types. |
| Lane mismatch | If using lanes, ensure the lane on recall matches the lane used at write time. |
Context block is empty or too small
| Cause | Fix |
|---|---|
Zero units with degraded=False | Check ContextBlock.scope: the read used a different project/env than the writes. |
degraded=True | Read reason; the matching warning was printed once earlier in the process. |
| Token budget too low | budget is enforced after assembly; the lowest-score units are dropped first. Raise it (inject.budget_tokens in the policy sets the default). |
| No relevant evidence | Ingest more data or broaden the task text. |
Runs and scoping
Run scope conflicts
Symptom: Data from one run appears in another, or data is missing.
- Runs scope memory writes. Lessons additionally carry a
scope(run,session,global) and are partitioned byenv. - Linked runs (
client.runs.link(...)) share data intentionally. Check whether runs are linked. - Inspect runs and their relationships with
client.runs.history(...)orGET /v2/control/runs; the Runs tab of the console lists loop runs with their injections and outcomes.
Cannot delete a run
Symptom: client.runs.delete(run_id) fails or data persists.
- Run deletion is permanent and removes all associated data.
- Ensure no active ingest jobs are running for the run.
- Both control and core planes can delete a run;
client.runs.deleteuses the control version, which handles both.
Lesson lifecycle
Lessons carry status (pending, active, rejected, retired) and scope (run, session, global). Every change is a transition with a reason, an actor and the policy_version in force; client.lessons.transitions(lesson_id=...) lists them and the console's lesson drawer renders the same timeline.
Promotion between scopes is governed by the effective policy (starter@1 by default) and by outcomes credited to the lesson; it is not tied to reflect(). Copying lessons between environments is an explicit action: client.policy.promote(from_env="dev", to_env="prod") or the Policy tab in the console.
Deprecation warnings
Symptom: mubit: client.query() is deprecated and will be removed in mubit-sdk 1.0; use client.raw.invoke("control.query") — https://docs.mubit.ai/sdk/migration#query
Every 0.13 name keeps working in 0.14 and prints one such line per call site. The link names the replacement; the full list is in Migration. To fail CI on any use: python -W error::mubit.MubitDeprecationWarning, MUBIT_DEPRECATIONS=error (JS) or #![deny(deprecated)] (Rust). mubit doctor prints the number of distinct deprecation warnings emitted in the current process.
Integration packages
Framework adapter not connecting
All integration packages (mubit-crewai, mubit-langgraph, etc.) use the core SDK internally. Verify:
MUBIT_API_KEYis setMUBIT_ENDPOINTpoints to a reachable Mubit instance- The adapter package version matches your SDK version (see Versioning)
Getting help
- SDK issues: github.com/mubit-ai/ricedb/issues
- API reference: Control HTTP | Control gRPC
- Configuration: SDK Configuration Reference