Skip to content

[Bug]: [webui] ERROR GET /api/crons ImportError: cannot import name 'atomic_write_text' from 'utils' (/app/venv/lib/python3.12/site-packages/utils.py) #82069

Description

@szmania

Bug Description

This endpoint returned error

[webui] ERROR GET /api/crons

Traceback (most recent call last):

  File "/app/server.py", line 382, in do_GET

    result = handle_get(self, parsed)

             ^^^^^^^^^^^^^^^^^^^^^^^^

  File "/app/api/routes.py", line 13510, in handle_get

    active_jobs, other_jobs = _cron_jobs_cross_profile(active_profile)

                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/app/api/routes.py", line 1459, in _cron_jobs_cross_profile

    from cron.jobs import list_jobs

  File "/home/hermeswebui/.hermes/hermes-agent/cron/__init__.py", line 18, in <module>

    from cron.jobs import (

  File "/home/hermeswebui/.hermes/hermes-agent/cron/jobs.py", line 42, in <module>

    from utils import atomic_replace, atomic_write_text

ImportError: cannot import name 'atomic_write_text' from 'utils' (/app/venv/lib/python3.12/site-packages/utils.py)

Using hermes-agent latest on a docker container.

hermes-webui latest on another container, and that container calls the hermes-agent cron api on the other container. Resulting int his error. I think its a get request to get all available cron jobs.

This endpoint should return the cron jobs.

I've tried starting the container with this: /app/venv/bin/pip uninstall utils -y and it did not work.

Steps to Reproduce

  1. Start the multi container setup
  2. Error occurs when trying to use the crons api via hermes-webui.

Expected Behavior

Crons api should be working as expected.

Actual Behavior

Crons api raises exception:

[webui] ERROR GET /api/crons

Traceback (most recent call last):

  File "/app/server.py", line 382, in do_GET

    result = handle_get(self, parsed)

             ^^^^^^^^^^^^^^^^^^^^^^^^

  File "/app/api/routes.py", line 13510, in handle_get

    active_jobs, other_jobs = _cron_jobs_cross_profile(active_profile)

                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/app/api/routes.py", line 1459, in _cron_jobs_cross_profile

    from cron.jobs import list_jobs

  File "/home/hermeswebui/.hermes/hermes-agent/cron/__init__.py", line 18, in <module>

    from cron.jobs import (

  File "/home/hermeswebui/.hermes/hermes-agent/cron/jobs.py", line 42, in <module>

    from utils import atomic_replace, atomic_write_text

ImportError: cannot import name 'atomic_write_text' from 'utils' (/app/venv/lib/python3.12/site-packages/utils.py)

Affected Component

Gateway (Telegram/Discord/Slack/WhatsApp)

Messaging Platform (if gateway-related)

No response

Debug Report

Don't feel comfortable pasting this publicly. 

But I can provide you anything else you need.

Operating System

Debian 14 "root@eac47d8c0095:/opt/hermes# cat /etc/os-release PRETTY_NAME="Debian GNU/Linux 13 (trixie)" NAME="Debian GNU/Linux" VERSION_ID="13" VERSION="13 (trixie)" VERSION_CODENAME=trixie DEBIAN_VERSION_FULL=13.4 ID=debian HOME_URL="https://www.debian.org/" SUPPORT_URL="https://www.debian.org/support" BUG_REPORT_URL="https://bugs.debian.org/""

Python Version

No response

Hermes Version

No response

Additional Logs / Traceback (optional)

Here is my compose:   

:
  hermes-agent-personal:
    #image: hermes-agent-local
    image: nousresearch/hermes-agent:latest
    #image: nousresearch/hermes-agent:v2026.5.16
    shm_size: "1g"
    container_name: hermes-agent-personal
    user: root
    restart: unless-stopped
    #command: /bin/bash /home/hermes/.hermes/start_gateways.sh
    command: gateway run
    # command: >
    #   bash -c "apt-get update && apt-get install -y openssh-server
    #   && mkdir -p /var/run/sshd
    #   && echo 'root:YOUR_PASSWORD_HERE' | chpasswd
    #   && sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
    #   && /usr/sbin/sshd -D
    #   && gateway run"
    ports:
      - "8642:8642"   # gateway API - default
      - "8643:8643"   # gateway API - investments
      - "38109:9119"   # dashboard (only reached when HERMES_DASHBOARD=1)
      - "2223:22"
    volumes:
      #- ~/.hermes:/opt/data
      # Persist config, state, sessions, skills, memory across restarts
      - ${HERMES_HOME}:/home/hermes/.hermes
      #- ${HERMES_WORKSPACE_BOOKS}:/home/hermes/.hermes/workspace/common/knowledge_base/BOOKS
      #- ${HERMES_WORKSPACE_BOOKS}:/home/hermes/.hermes/workspace/common/knowledge_base/BOOKS:ro
      #- /home/omv/.hermes:/home/hermes/.hermes
      # Expose agent source so the WebUI can install dependencies from it
      - hermes-agent-src-personal:/opt/hermes
      #- hermes-uv-cache:/home/hermes/.cache/uv          # ← add this
      #- hermes-py-venv:/app/venv
      # Run before Hermes starts
      - ${HERMES_HOME}/03-install-deps:/etc/cont-init.d/03-install-deps:ro
    # env_file:
    #   - /home/omv/.hermes_personal/.env
    environment:
      - HERMES_DASHBOARD=${HERMES_DASHBOARD}
      - HERMES_DASHBOARD_TUI=${HERMES_DASHBOARD_TUI}
      - HERMES_TUI_DIR=${HERMES_TUI_DIR}
      - API_SERVER_ENABLED=${API_SERVER_ENABLED}
      - API_SERVER_HOST=${API_SERVER_HOST}
      - API_SERVER_KEY=${API_SERVER_KEY}
      - GATEWAY_ALLOW_ALL_USERS=${GATEWAY_ALLOW_ALL_USERS}
      - HERMES_HOME=${HERMES_AGENT_HOME}
      - HERMES_UID=${HERMES_UID}
      - HERMES_GID=${HERMES_GID}
      - HERMES_DASHBOARD_INSECURE=${HERMES_DASHBOARD_INSECURE}
      #- TERMINAL_CWD=${HERMES_AGENT_HOME}/workspace
      - HERMES_DASHBOARD_BASIC_AUTH_USERNAME=${HERMES_DASHBOARD_BASIC_AUTH_USERNAME}
      - HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH=${HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH}
      - HERMES_DASHBOARD_BASIC_AUTH_SECRET=${HERMES_DASHBOARD_BASIC_AUTH_SECRET}
      
      # Uncomment to forward specific env vars instead of using .env file:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - OPENAI_API_BASE_URL=${OPENAI_API_BASE_URL}
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
    deploy:
      resources:
        limits:
          memory: 4G
          cpus: "4.0"    
    networks:
      - hermes-net-personal
      
  hermes-webui-personal-investments:
    image: ghcr.io/nesquena/hermes-webui:latest
    #image: hermes-webui-local:latest
    container_name: hermes-webui-personal-investments
    user: root
    shm_size: "1g"
    ports:
      # Expose on localhost only. Remove 127.0.0.1: to expose on all interfaces
      # (set HERMES_WEBUI_PASSWORD if doing so).
      - "38111:8787"
    volumes:
      # Same hermes home as the agent — shares config, sessions, state
      - ${HERMES_HOME_INVESTMENTS}:/home/hermeswebui/.hermes
      # Agent source mounted where docker_init.bash expects it.
      # Mounted read-only — the WebUI only reads this volume to install
      # the agent's Python dependencies at startup (`uv pip install`).
      # Read-only enforces that defence-in-depth at the kernel layer.
      - hermes-agent-src-personal:/home/hermeswebui/.hermes/hermes-agent:ro
      # Workspace directory — browse and edit files from the WebUI.
      # Adapt the host path to your project directory.
      # ${HOME} is used rather than `~` so the default resolves the same way
      # across Linux, macOS, WSL2, and Docker Desktop on Windows.
      - ${HERMES_HOME}/profiles/investments/workspace:/workspace
      - hermes-uv-cache:/uv_cache          # ← add this
      - hermes-py-venv:/app/venv
    # env_file:
    #   - /home/omv/.hermes_personal/.env
    environment:
      - HERMES_WEBUI_HOST=${HERMES_WEBUI_HOST}
      - HERMES_WEBUI_PORT=${HERMES_WEBUI_PORT}
      - HERMES_WEBUI_STATE_DIR=${HERMES_WEBUI_STATE_DIR_INVESTMENTS}
      - HERMES_WEBUI_PASSWORD=${HERMES_WEBUI_PASSWORD}
      - HERMES_HOME=${HERMES_WEBUI_HOME}
      # Match your host user's UID/GID for correct file permissions.
      # Run `id -u` and `id -g` to find your values.
      # On macOS, UIDs start at 501 (not 1000) — set these in a .env file:
      #   echo "UID=$(id -u)" >> .env && echo "GID=$(id -g)" >> .env
      - WANTED_UID=${HERMES_UID}
      - WANTED_GID=${HERMES_GID}
      # NOTE: When using bind-mount volumes shared across containers, ALL containers
      # that write to the same host directory must run as the same UID/GID.
      # If hermes-agent initialises the state dir as root (UID 0), hermes-webui
      # will get a PermissionError accessing those paths — including a crash on every
      # HTTP request if the auth signing-key file is unreadable. Either set WANTED_UID
      # to match the agent container's UID, or use a named Docker volume (preferred).
      # Optional: set a password for remote access
      # - HERMES_WEBUI_PASSWORD=your-secret-password
      # Bind-mount permission handling for the WebUI (fixes #1389, #1399).
      # NOTE: WebUI's HERMES_HOME_MODE is a credential-file threshold (allow
      # group bits on .env/.signing_key/etc.), DIFFERENT from the agent's
      # which applies to the HERMES_HOME directory itself. 0640 is correct
      # for the WebUI; do NOT copy this value to the agent service block.
      # - HERMES_SKIP_CHMOD=1
      # - HERMES_HOME_MODE=0640
      - HERMES_API_URL=${HERMES_API_URL_INVESTMENTS}
      - GATEWAY_HEALTH_URL=${HERMES_API_URL_INVESTMENTS}
      - HERMES_WEBUI_CHAT_BACKEND=gateway
    restart: unless-stopped
    networks:
      - hermes-net-personal
    depends_on:
      - hermes-agent-personal
    command: >
      /bin/sh -c "
      apt-get update &&
      apt-get install -y nodejs npm passwd &&
      export PATH=/usr/sbin:/usr/bin:/bin:$PATH &&
      npm install -g @playwright/test &&
      npm install -g @playwright/cli@latest &&
      npx playwright install --with-deps &&
      npx -y patchright-mcp@latest &&
      npm install -g @icoretech/warden-mcp@latest &&
      curl -LsSf https://astral.sh/uv/install.sh | sh &&
      export PATH=$HOME/.local/bin:$PATH &&
      . /root/.local/bin/env &&
      uv tool install mcp-server-linkedin@latest &&
      uv tool install zulipchat-mcp &&
      uv tool install patchright &&
      uv tool update-shell &&
      /app/venv/bin/pip uninstall utils -y &&
      export PATH=$HOME/.local/bin:/usr/sbin:/usr/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH &&
      /hermeswebui_init.bash
      "
      
networks:
  hermes-net-personal:
    driver: bridge

volumes:
  # IMPORTANT — upgrading the agent image:
  #   The `hermes-agent-src` volume is initialised from the agent image's
  #   `/opt/hermes` on first `up`, and Docker reuses the volume verbatim on
  #   later runs — even after `docker pull` of a newer agent image. After
  #   upgrading the agent image, run:
  #
  #     docker compose -f docker-compose.three-container.yml down
  #     docker volume rm <project>_hermes-agent-src
  #     docker compose -f docker-compose.three-container.yml pull
  #     docker compose -f docker-compose.three-container.yml up -d
  #
  #   The full procedure (and why) is documented in docs/docker.md.
  hermes-agent-src-personal:
  hermes-uv-cache:                          # ← and declare it
  hermes-py-venv:`

Root Cause Analysis (optional)


The recreated-volume result shows cron, cron.jobs, and utils all resolving from the WebUI venv's site-packages. That rules out the earlier stale-volume/path-order hypothesis. The packaged Hermes Agent cron module is importing an unqualified top-level utils, and the installed environment resolves an unrelated package by that name instead of the Agent-owned atomic-write helper. Uninstalling arbitrary utils packages is not a durable WebUI fix.

The correct boundary is upstream packaging/import ownership: use a package-relative Agent utility import (or package the helper under an unambiguous Agent namespace), with a clean-wheel regression that imports cron.jobs in an environment where an unrelated utils distribution is installed. I removed needinfo and needs-reproduction; keeping investigation, priority, docker, and upstream-change while that upstream fix is routed.```

See more here on issue created in hermes-webui which determined a hermes-agent issue. https://github.com/nesquena/hermes-webui/issues/6811#issuecomment-5227884743

### Proposed Fix (optional)

_No response_

### Are you willing to submit a PR for this?

- [ ] I'd like to fix this myself and submit a PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existsarea/dockerDocker image, Compose, packagingbugcomp/cronCron scheduler and job managementtype/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions