# Troubleshooting

> What each pyrula dev check means and the fix it prints.

Source: https://docs.pyrula.io/getting-started/troubleshooting/

<!-- Generated by scripts/gen_troubleshooting.py from the diagnostics catalog. Do not edit. -->

`pyrula dev` runs these checks before it starts and prints only the ones that warn or
fail. `pyrula dev --check` prints all of them and exits 1 if any failed; add `--json`
for a machine-readable summary. Neither prints your key, a token, a URL credential or
anything a run handled.

```bash
pyrula dev --check
```

Each line is `[status] id: detail`, and a warning or failure adds a `fix:` line.
Angle brackets in the fixes below stand for a value from your setup.

## Examples

A key that is not a project key:

```bash
PYRULA_CLOUD_API_KEY=oops pyrula dev --check
```

```text
[fail] key: malformed
  fix: That is not a project key (pyr_<env>_...); a CI token (PYRULA_TOKEN) does not work here.
```

An origin that does not answer:

```bash
PYRULA_CLOUD_API_KEY="<key>" PYRULA_CLOUD_URL=http://127.0.0.1:9 pyrula dev --check
```

```text
[fail] origin: http://127.0.0.1:9 unreachable
  fix: Cannot reach http://127.0.0.1:9: ConnectError
```

## Checks

### `python`

The interpreter is Python 3.11 to 3.15 and not the free-threaded build.

- `python.unsupported`: Python <version> is unsupported; use 3.11-3.15 (not the free-threaded build).

### `packages`

Which pyrula packages are installed, that the Cloud client is present when a key is set, that their versions agree, and that the web stack `pyrula dev` serves with is installed.

- `packages.client_missing`: PYRULA_CLOUD_API_KEY is set but pyrula-cloud-client is not installed. Install it: pip install "pyrula[cloud]"
- `packages.floor`: <dependent> needs <requirement>, but <installed_name> <installed> is installed. Run: pip install -U <dependent> <installed_name>
- `packages.ui_missing`: pyrula dev needs <module>, a dependency of pyrula-workflows, and it is not installed (an install with --no-deps?). Run: pip install <module>

### `key`

`PYRULA_CLOUD_API_KEY` is present and shaped like a project key. Only the key marker and key id are ever printed.

- `key.absent`: No PYRULA_CLOUD_API_KEY - running locally only. To see runs in Cloud, copy the key from Setup.
- `key.malformed`: That is not a project key (pyr_<env>_...); a CI token (PYRULA_TOKEN) does not work here.

### `origin`

`PYRULA_CLOUD_URL` (or the default origin) is a valid URL, and with `--check` that it answers like a Pyrula API.

- `origin.url_without_key`: PYRULA_CLOUD_URL is set but PYRULA_CLOUD_API_KEY is missing. Set the key, or unset the URL to run locally.
- `origin.bad_url`: <message>
- `origin.unreachable`: Cannot reach <origin>: <error>
- `origin.not_api`: <origin> is not a Pyrula API - use the API URL, not the dashboard URL.
- `origin.bad_status`: <origin> answered <status> to the config read, so it may be down or behind a redirect; use the final https API URL.
- `origin.mismatch`: <origin> is not the URL this deployment publishes for itself. Set PYRULA_CLOUD_URL to <public_url>.

### `register`

Whether Cloud accepted the key. `--check` never registers a runtime, so it reports `skipped`; the answer appears when `pyrula dev` starts. A running runtime that Cloud starts refusing (401) holds its events and keeps retrying, so once Cloud accepts the key again it needs no restart: after a short refusal it re-registers within a minute (about 35 s in testing, a heartbeat tick plus a random offset of up to 30 s each) and delivers what it held within a minute after that (about 60 s from the fix). Each further refusal doubles the wait, up to 5 minutes, so after a long one allow about 7 minutes, or restart the process to register at once.

- `register.refused`: Cloud refused this key: it may be revoked, expired, or from another deployment. Reveal or create one in Setup for the project and environment you want.
- `register.unreachable`: Cannot reach <origin> to register; retrying with backoff.
- `register.rejected`: <origin> answered <status> to register, which is not a refusal of the key; retrying with backoff.
- `register.no_token`: <origin> answered register without a runtime token, so nothing can be sent. Check that PYRULA_CLOUD_URL points at the Cloud API, not the dashboard; retrying with backoff.

### `control`

Whether Cloud control is switched off.

- `control.off`: Cloud is disabled by PYRULA_CLOUD_CONTROL=off.

### `store`

Which store the run uses: that a SQLite file opens (once it exists), or that a Valkey answers and does not evict run state. An `allkeys-*` policy fails the check; a `volatile-*` one warns, since it can drop only finished-run history and a few short-lived keys, and on [ElastiCache Serverless](https://docs.pyrula.io/getting-started/managed-valkey.md#aws-elasticache-serverless), which fixes it at `volatile-lru`, the warning names what to alarm on. A Valkey the store could not open (the connection refused or timed out, Lua scripting denied, the password rejected, a cluster behind a proxy) fails with the store's own message under `store.unopenable`. The message carries the reason and, where it is known, the fix: the grant to add or the flag to pass. `store.unreachable` is for a failure that carries no message.

- `store.memory`: Store: in-memory - Cloud shows this run, but recovery needs a durable store (drop --store memory:// to use .pyrula/dev.db, or pass --store URL)
- `store.unreachable`: Cannot reach Valkey at <url>
- `store.unreachable_reason`: Cannot reach Valkey at <url>: <reason>
- `store.denied`: Valkey at <url> refused a command: <reason>. Grant the user that command (ACL SETUSER <user> +<command>) or use a user that has it.
- `store.unopenable`: Cannot open the store <url>: <reason>
- `store.archive`: Cannot use the Postgres archive from --postgres-dsn (<dsn>): <reason>. Check the host, port, user, password and database in the DSN.
- `store.eviction`: Valkey maxmemory-policy is <policies>, so once memory fills it may evict keys pyrula keeps (a volatile-* policy only finished-run history and short-lived keys). <fix>
- `store.wake_push`: valkey: push wake-ups unavailable (<reason>); polling at each worker's claim_poll_interval. Idle workers still claim every run, at that interval instead of the moment it is queued. The usual cause is a proxy that does not forward blocking commands or a user without +@blocking (ACL SETUSER <user> +blpop).

### `outbox`

The on-disk spool for critical events can be created and written.

- `outbox.off`: PYRULA_CLOUD_OUTBOX_PATH=off: critical events last only while this process runs.
- `outbox.unwritable`: The outbox directory is not writable; set PYRULA_CLOUD_OUTBOX_PATH to a path this user can write, or to off.
- `outbox.quarantined`: The outbox file was damaged, so a fresh one was started. Events it had not yet sent stay in the moved file and are not sent from there.
- `outbox.open_failed`: The outbox could not be opened, so critical events last only while this process runs. Check PYRULA_CLOUD_OUTBOX_PATH.

### `provider`

For an agent target, whether a model is configured and a provider key and package are present. It never calls the provider.

- `provider.none_configured`: No model is configured for this target. Set llm= on @agent, or pass --llm-ref.
- `provider.bad_ref`: --llm-ref <ref> does not resolve to a model instance. Pass module:name of an LLM object, e.g. your_module:llm.
- `provider.no_key`: No provider key found. Set ANTHROPIC_API_KEY or OPENAI_API_KEY, or the variable your own client reads.
- `provider.package_missing`: A provider key is set but its package is not installed: pip install <packages>

### `binding`

A store bound to a different project than the key's. Read by `--check` only, and only with a key and a durable store.

- `binding.mismatch`: This store is bound to project <bound_project> (<bound_environment>), not the key's project <key_project>. Run: pyrula store binding show

### `target`

The app file `pyrula dev` was pointed at: it must import cleanly and hold the named target, or exactly one when none is named. Read by `--check` only.

- `target.unloadable`: Fix the error above, or name the target with pyrula dev app.py:<name>, then rerun pyrula dev --check.

## Traces accepted but no run

That is not a `dev` check: it concerns an OpenTelemetry exporter. See
[Send existing traces](https://docs.pyrula.io/getting-started/otel.md#intake-accepted-but-no-run).
