Skip to content

Docker deploy: hooks crash every turn, DB skills broken, scheduler never runs — all rooted in bare python3 vs uv venv #110

Description

@mariltonleal

Summary

When deployed via Docker (Dockerfile.swarm.dashboard + docker-compose.yml, which install Python deps with uv sync --no-dev into a venv at /workspace/.venv), several core features are broken out of the box. The common root cause for most of them: code paths invoke the system python3 instead of the venv interpreter, and the system interpreter has none of the project dependencies.

Environment: @evoapi/evo-nexus v0.32.3, dashboard image built from Dockerfile.swarm.dashboard, single-container deploy via docker-compose.yml, Claude Code authenticated via a long-lived OAuth token (CLAUDE_CODE_OAUTH_TOKEN from claude setup-token).

Note: /workspace/.venv/bin/python is a symlink to the system python3, but invoking it via the venv path activates the venv site-packages (through the adjacent pyvenv.cfg). Invoking bare python3 does not.


Bug 1 — Claude Code hooks crash every turn (Stop hook error occurred) — critical

dashboard/backend/claude_hook_bootstrap.py writes the hook command from:

DISPATCHER_CMD_TEMPLATE = (
    'python3 "$CLAUDE_PROJECT_DIR/dashboard/backend/claude_hook_dispatcher.py" {event}'
)

claude_hook_dispatcher.py does an unconditional module-top import yaml. Under the Docker/uv install the system python3 lacks yaml, so every PreToolUse/PostToolUse/Stop/SubagentStop hook dies with ModuleNotFoundError: No module named 'yaml', surfaced to the user as a Stop hook error occurred chat notification on every agent turn.

Repro

docker compose exec -T -w /workspace dashboard sh -c \
  'echo "{}" | CLAUDE_PROJECT_DIR=/workspace python3 dashboard/backend/claude_hook_dispatcher.py Stop'
# => ModuleNotFoundError: No module named 'yaml'

Two-part fix (both required):

  1. Use the venv interpreter in the template:
    DISPATCHER_CMD_TEMPLATE = (
        '"$CLAUDE_PROJECT_DIR/.venv/bin/python" '
        '"$CLAUDE_PROJECT_DIR/dashboard/backend/claude_hook_dispatcher.py" {event}'
    )
  2. Make run() reconcile existing managed entries. It is currently idempotent-by-presence on the _evonexus_managed sentinel (if not _has_managed_hook(entries): append), so it never rewrites a stale command already baked into settings.json. It should compare each managed hook's command against the current template and overwrite in place on mismatch (preserving non-managed entries such as agent-tracker.sh).

Also affects claude_hook_dispatcher.py::_runner_cmd, which launches plugin .py handlers with ["python3", handler_path] under a stripped env (no PYTHONPATH/VIRTUAL_ENV) — same failure once a plugin handler imports any non-stdlib dep.


Bug 2 — db-mysql / db-mongo / db-redis skills are non-functional (undeclared drivers) — high

These skills import pymysql / pymongo / redis (and pymongo needs dnspython for the documented mongodb+srv:// Atlas URIs), but none are declared in pyproject.toml / uv.lock, so they are absent from the venv and every test/query returns driver_missing. Only db-postgres works, and only because psycopg2-binary is pulled in transitively.

The SKILL.md examples additionally instruct python3 ...db_client.py (system interpreter) and uv pip install <driver> "on first use" — the latter is ephemeral and lost on every image rebuild.

Fix: declare redis, pymongo, dnspython, pymysql in pyproject.toml; change the SKILL.md examples to uv run python ....


Bug 3 — Recurring scheduler never runs in the Docker deployment — high

The active docker-compose.yml omits the scheduler service that the sibling docker-compose.hub.yml / .proxy.yml / evonexus.stack.yml include, and start-dashboard.sh (the container entrypoint's CMD) launches only the terminal-server and Flask — never scheduler.py. Result: every recurring ADW routine (good_morning, end_of_day, memory_sync, memory_lint, backup, and config/routines.yaml) silently never fires.

Knock-on: plugin routine activation is permanently stuck at routine_activation_pending because plugin_loader expects a scheduler.pid that never exists.

Repro

docker compose exec -T dashboard sh -c \
  'for p in /proc/[0-9]*; do tr "\0" " " < $p/cmdline; echo; done' | grep scheduler.py
# => (no output)

Fix: ship a scheduler in the default compose. Note two subtleties if done as a separate service: (a) plugin_loader's SIGHUP-based hot-reload cannot cross container PID namespaces, and (b) the sibling composes mount ADWs/logs as a named volume the dashboard container cannot see (it uses a host bind), so scheduler.pid is invisible cross-container. Launching scheduler.py as a sibling process inside the dashboard container (same PID namespace + same ADWs/logs mount) sidesteps both. Either way it must use uv run python / the venv, never bare python3.


Bug 4 — REQUIRE_ANTHROPIC_KEY gate ignores OAuth token — medium

entrypoint.sh loops forever waiting only for ANTHROPIC_API_KEY:

while [ -z "${ANTHROPIC_API_KEY:-}" ]; do ... sleep 30 ... done

A deployment authenticated via CLAUDE_CODE_OAUTH_TOKEN (from claude setup-token) has no ANTHROPIC_API_KEY, so any service with REQUIRE_ANTHROPIC_KEY=1 (e.g. the scheduler/telegram services in the sibling composes) hangs forever.

Fix:

while [ -z "${ANTHROPIC_API_KEY:-}" ] && [ -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]; do ...

Bug 5 — Scheduler UI suggests make run R=<id> but the image has no makelow

dashboard/backend/routes/scheduler.py emits a command field of the form make run R=<id> (surfaced as a copy-pasteable command in Scheduler.tsx), but the python:3.12-slim-based runtime image never installs make, so the suggested command fails with make: not found.

Fix: emit uv run python ADWs/routines/<script> (or install make).


Bug 6 — npx updater cannot update a Docker/compose install — low

The updater does git merge --ff-only, which aborts on any locally-modified tracked file, and its restart path targets a systemd unit / start-services.sh that don't exist in a Docker deployment. So a compose-based install can't be updated through it.


Suggested consolidated direction

Introduce a single interpreter resolver used everywhere a routine/hook/handler is launched — prefer the absolute venv path $WORKSPACE/.venv/bin/python when it exists, fall through to uv run python, never bare python3 — and declare all runtime drivers in pyproject.toml. I've applied all of the above locally and confirmed each fix; happy to open a PR if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions