# Troubleshooting Common problems and their solutions. If your issue is not here, check the logs — EDRI logs at `DEBUG` level by default, and most components emit detailed messages. --- ## Event not delivered to Manager **Symptom:** You send an event to the router queue but the Manager's `@request` handler is never called. **Causes and fixes:** 1. **Manager not registered** — verify you called `edri.add_component(MyManager(edri.router_queue))` before `edri.run()`. 2. **Handler type annotation missing or wrong** — `_prepare_resolvers()` reads the type annotation of the first parameter, which must be named `event` and annotated with an `Event` subclass. If the parameter is unnamed/misnamed or the annotation is missing or not an `Event` subclass, `_prepare_resolvers()` raises `AttributeError` at startup and the Manager process exits before its event loop starts (so it never handles anything). ```python # Wrong — missing annotation @request def handle(self, event) -> None: ... # Correct @request def handle(self, event: MyEvent) -> None: ... ``` 3. **Event type mismatch** — the Router dispatches by **exact Python type**. If you put a subclass on the queue but the Manager subscribes to the parent class, the subclass event will not match. Subscribe to the exact type you emit, or subscribe to each subclass separately. 4. **Manager crashed on startup** — if `after_start()` raises an exception, the Manager may exit before entering its event loop. Check the process stderr or configure logging. 5. **`from_time` filtering** — if the Manager was started with `from_time=datetime(...)`, events timestamped before that datetime are silently discarded. This is intentional for replaying missed events after reconnect. --- ## `pickle` error when putting event on queue **Symptom:** `_pickle.PicklingError: Can't pickle ...` or similar. **Cause:** An event field holds a value that cannot be pickled. Common culprits: * Lambda functions as field defaults or values. * File handles, socket objects, database connections. * Generator objects. * Locally-defined classes that Python cannot resolve. **Fix:** Replace unpicklable values with picklable representations: ```python # Wrong @event class Task(Event): callback: Callable # lambda is not picklable # Correct — pass the data, call the function on the receiver side @event class Task(Event): data: dict ``` For file content, use `SharedMemoryPipe` (see {doc}`/api_download`) rather than passing file handles directly. --- ## Response never reaches the API client (request times out) **Symptom:** REST or WebSocket request hangs and eventually times out (`EDRI_API_RESPONSE_TIMEOUT`). **Checklist:** 1. **Handler does not modify `event.response`** — if no field is set and `set_status()` is never called, `has_response()` remains `False` and the auto-send is skipped. The API layer waits indefinitely. Add at minimum: ```python event.response.set_status(ResponseStatus.OK) ``` 2. **Worker forgets to call `event_send(event)`** — unlike Manager handlers, Workers do not auto-send. If a Worker modifies `event.response` but omits `self.event_send(event)`, the Manager never receives the response. 3. **Manager emits a new event instead of responding** — if the handler puts a new event on `router_queue` but does not set `event.response`, the original request has no response. 4. **Exception in handler** — if the handler raises an unhandled exception, the event lifecycle stops (the caller gets no response). The Manager itself does not crash: the exception is captured and surfaced on the next health cycle as `WARNING` in {ref}`health-status-endpoint`. Enable DEBUG logging to see the traceback, or wrap handler bodies in try/except and set an error directive if you want a proper client response: ```python from edri.dataclass.directive.base import InternalServerErrorResponseDirective @request def handle(self, event: MyEvent) -> None: try: ... except Exception as e: self.logger.exception("Unhandled error", exc_info=e) event.response.set_status(ResponseStatus.FAILED) event.response.add_directive(InternalServerErrorResponseDirective(str(e))) ``` --- ## REST endpoint returns HTML instead of JSON **Symptom:** A curl request receives HTML when JSON is expected. **Fix:** Add `Accept: application/json` to the request: ```bash curl -H "Accept: application/json" http://localhost:8080/items ``` Without the `Accept` header, or with `Accept: */*`, the Listener may prefer the HTML renderer if an HTML endpoint exists at the same URL. See {doc}`/api_content_negotiation`. --- ## Validation returns 400 but error message is unclear **Symptom:** A validation failure returns HTTP 400, but the body shows only one reason (or a terse exception string) and you can't tell what really failed. **Explanation:** The 400 body carries the validation failures as a `reasons` list. How much of it is exposed is controlled by **`EDRI_API_EXPOSE_ERROR_DETAILS`**, which defaults to the runtime mode (`ENVIRONMENT`): * **development** (`true`) — the **full chain** of reasons is returned, including the raw exception strings and the field that failed. Good for debugging. * **production** (`false`) — the `reasons` list is truncated to the **first** entry, so internal details are not leaked. With several fields failing at once you only see the first one — which is why a production 400 can look "unclear". So a message that is detailed in dev but terse in prod is expected behavior, not a bug. To get the full detail in a given environment, set the toggle explicitly: ```bash export EDRI_API_EXPOSE_ERROR_DETAILS=true ``` The full (development) format looks like: ``` Error 400: Cannot create ValueError("Wrong type ...") ``` The field name that failed validation is included in the reason. To reshape error bodies beyond this toggle (a custom schema, masking), use a response {doc}`/middleware`. See {doc}`/env`. --- ## WebSocket client does not receive server-push events **Symptom:** A Manager puts a server-push event on the router queue but the WS client never receives it. **Checklist:** 1. **Client did not register** — the client must send a `register` command before it will receive subscribed events. See {doc}`/websocket`. 2. **`param_set` mismatch** — the client registered with `{"project_id": 10}` but the emitted event has `project_id=11`. Values must match exactly. 3. **Wrong `handling` type** — the event's `@api(handling=...)` must include `SUBSCRIBED` for subscription-based delivery. If `handling=EventHandlingType.SPECIFIC`, only the originating client receives it. 4. **Event not in `api_events`** — the Broker only forwards events defined with `@api(...)`. Fire-and-forget internal events are not forwarded to WS clients. 5. **Broker can't route it** — enable DEBUG logging on the Broker (`edri.api.broker`) to trace event routing: ```python import logging logging.getLogger("edri.api.broker").setLevel(logging.DEBUG) ``` --- ## HealthChecker marks Manager as WARNING or ERROR **Symptom:** Log shows `Manager X: WARNING` or `Manager X: ERROR`. **Meaning:** The Manager missed one or more health-check pings. `EDRI_HEALTH_CHECK_FAILURE_LIMIT` missed pings (default: 3) triggers `ERROR`. **Checklist:** 1. **Manager is deadlocked** — if the Manager's handler calls something that blocks (network I/O, `time.sleep`, a lock), subsequent events — including health checks — queue up and appear as missed. Fix: move blocking work to a Worker (thread or process). 2. **Worker limit reached** — if `max_workers` is set and all slots are full, new events including health checks are processed only after a worker finishes. 3. **Manager process exited** — check if the Manager raised an unhandled exception. Enable logging at DEBUG level. **Adjusting sensitivity:** ```bash # Check every 30 seconds, allow 5 missed pings before ERROR export EDRI_HEALTH_CHECK_TIMEOUT=30 export EDRI_HEALTH_CHECK_FAILURE_LIMIT=5 ``` --- ## Scheduler job does not fire **Symptom:** A scheduled job never fires even though `when` is in the past. **Checklist:** 1. **Scheduler not started** — confirm `edri.start_scheduler()` is in your startup code. 2. **Timezone mismatch** — if you pass a naive `datetime` (no `tzinfo`), EDRI converts it to local time. If the system timezone is not what you expect, the job fires at the wrong absolute time. Use timezone-aware datetimes: ```python from datetime import datetime, timezone when = datetime.now(tz=timezone.utc) + timedelta(minutes=5) ``` 3. **`repeat` anchoring** — if the system is overloaded and a job fires late, `when` is still advanced by the original `repeat` interval, not from the actual firing time. This prevents drift but means a very delayed job may fire again almost immediately. --- ## Connector fails to reconnect to the Switch **Symptom:** Log shows repeated `Connection refused` or `Connection reset` messages for the Switch. **Checklist:** 1. **Switch not running** — start the Switch with `python -m edri.switch.switch` or via Docker. 2. **Wrong host/port** — check `EDRI_SWITCH_HOST` and `EDRI_SWITCH_PORT` match the Switch's bind address. 3. **Firewall** — ensure the port is open between the EDRI instance and the Switch host. 4. **Events accumulate in cache** — while disconnected, the Router stores events for up to `EDRI_CACHE_TIMEOUT` seconds. Events older than the timeout are dropped. Increase `EDRI_CACHE_TIMEOUT` if short network interruptions are causing event loss. --- ## `mypy` type errors in Manager or Worker subclasses **Symptom:** `mypy` reports errors about `@request` / `@response` decorated methods or unresolved types. **Notes:** * `ManagerBaseMeta` renames decorated methods at class creation time (`handle → solve_req_handle`). This confuses static analysis tools that do not understand metaclass transformations. * The project runs mypy in strict mode (configured in `pyproject.toml`). The `edri/switch/` subtree is excluded. * Type-annotate handler parameters explicitly — mypy needs the annotation to resolve event types. * If you use `inject(...)` as a field type, mypy may not resolve the return type automatically. Add an explicit cast or use `# type: ignore` only on that line. --- ## See also * {doc}`/deployment` — production configuration checklist * {doc}`/env` — all environment variables * {doc}`/manager` — Manager lifecycle, `after_start()`, caching * {doc}`/worker` — Worker communication, `event_send()` * {doc}`/middleware` — middleware execution order * {doc}`/specialized_managers` — Scheduler and Store behavior