bytedance/deer-flow · critical · RuntimeError

Failed to read JWT secret from {secret_file}. Set AUTH_JWT_S

Error message

Failed to read JWT secret from {secret_file}. Set AUTH_JWT_SECRET explicitly or fix DEER_FLOW_HOME/base directory permissions so DeerFlow can read its persisted auth secret.

What it means

When no AUTH_JWT_SECRET env var is set, the Gateway derives its JWT signing secret from {base_dir}/.jwt_secret. If that file exists but reading it raises OSError (permission denied, SELinux/AppArmor denial, I/O error), this RuntimeError is raised, chaining the original exception. The two remedies in the message are: set the env var explicitly, or fix directory ownership/permissions under DEER_FLOW_HOME.

Source

Thrown at backend/app/gateway/auth/config.py:48


_auth_config: AuthConfig | None = None


def _load_or_create_secret() -> str:
    """Load persisted JWT secret from ``{base_dir}/.jwt_secret``, or generate and persist a new one."""
    from deerflow.config.paths import get_paths

    paths = get_paths()
    secret_file = paths.base_dir / _SECRET_FILE

    try:
        if secret_file.exists():
            secret = secret_file.read_text(encoding="utf-8").strip()
            if secret:
                return secret
    except OSError as exc:
        raise RuntimeError(f"Failed to read JWT secret from {secret_file}. Set AUTH_JWT_SECRET explicitly or fix DEER_FLOW_HOME/base directory permissions so DeerFlow can read its persisted auth secret.") from exc

    secret = secrets.token_urlsafe(32)
    try:
        secret_file.parent.mkdir(parents=True, exist_ok=True)
        fd = os.open(secret_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
        with os.fdopen(fd, "w", encoding="utf-8") as fh:
            fh.write(secret)
    except OSError as exc:
        raise RuntimeError(f"Failed to persist JWT secret to {secret_file}. Set AUTH_JWT_SECRET explicitly or fix DEER_FLOW_HOME/base directory permissions so DeerFlow can store a stable auth secret.") from exc
    return secret


def get_auth_config() -> AuthConfig:
    """Get the global AuthConfig instance. Parses from env on first call."""
    global _auth_config
    if _auth_config is None:
        from dotenv import load_dotenv

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Fix ownership/permissions: `chown -R <gateway-user> <DEER_FLOW_HOME>` and ensure the data dir is readable (e.g. 0700 dir, 0600 file owned by the service user).
  2. Alternatively set AUTH_JWT_SECRET in the environment to a stable value and skip the file path entirely.
  3. If on NFS/SELinux, verify the mount/options permit the service user's reads.
  4. Note: an empty-but-readable file falls through to regeneration (a different error, 79, covers write failure).

Example fix

# before: data dir owned by root, gateway runs as deerflow
docker compose up   # -> Failed to read JWT secret ...

# after
sudo chown -R 1000:1000 ./deer-flow-data
docker compose up
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import Path

def jwt_secret_readable(base_dir: Path) -> bool:
    f = base_dir / '.jwt_secret'
    if not f.exists():
        return True  # nothing to read yet
    try:
        return bool(f.read_text(encoding='utf-8').strip())
    except OSError:
        return False

Try / catch

try:
    secret = load_jwt_secret()
except RuntimeError as e:
    if 'Failed to read JWT secret' in str(e):
        secret = os.environ['AUTH_JWT_SECRET']  # documented escape hatch
    else:
        raise

Prevention

When it happens

Trigger: The Gateway process (often containerized, running as a non-root user) cannot read .jwt_secret because the file or an ancestor directory is owned by root with restrictive modes; DEER_FLOW_HOME points at a read-only or NFS-mounted volume with broken permissions; security modules blocking the read.

Common situations: Docker bind-mount of the data dir created by root on the host but the container runs as another UID; moving a data directory between hosts with `sudo cp` losing ownership; read-only mounts in hardened deployments.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/bee1901a487874c5. Report an issue: GitHub.