bytedance/deer-flow · critical · RuntimeError

Failed to persist JWT secret to {secret_file}. Set AUTH_JWT_

Error message

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.

What it means

The complement of the read error: no persisted secret was found (or it was empty), so the Gateway generated a fresh secrets.token_urlsafe(32) and tries to persist it to {base_dir}/.jwt_secret with 0o600 via a low-level os.open. If mkdir of the parent, file creation, or the write raises OSError, this RuntimeError is raised. Failing here matters because without persistence every restart mints a new secret and invalidates all issued JWTs.

Source

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

    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

        load_dotenv()
        jwt_secret = os.environ.get("AUTH_JWT_SECRET")
        if not jwt_secret:
            jwt_secret = _load_or_create_secret()
            os.environ["AUTH_JWT_SECRET"] = jwt_secret
            logger.warning(
                "⚠ AUTH_JWT_SECRET is not set — using an auto-generated secret "
                "persisted to .jwt_secret. Sessions will survive restarts. "
                "For production, add AUTH_JWT_SECRET to your .env file: "

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Ensure the Gateway user can create and write files under DEER_FLOW_HOME/base_dir: `chown`/`chmod 700` the directory, mount a writable volume in Docker.
  2. Point DEER_FLOW_HOME at a writable location (per-host persistent path) so the secret survives restarts.
  3. As a fallback, set AUTH_JWT_SECRET env var explicitly so persistence is not required.
  4. Check disk space and filesystem mount flags (ro) if permissions look correct.

Example fix

# before: read-only mount
docker run ... -v /opt/deerflow:/deerflow:ro deerflow

# after: writable persistent volume
docker run ... -v /opt/deerflow:/deerflow deerflow
Defensive patterns

Strategy: validation

Validate before calling

import os, tempfile

def base_dir_writable(base_dir: Path) -> bool:
    try:
        base_dir.mkdir(parents=True, exist_ok=True)
        with tempfile.TemporaryFile(dir=base_dir):
            pass
        return True
    except OSError:
        return False

Try / catch

try:
    secret = load_or_persist_jwt_secret()
except RuntimeError as e:
    if 'Failed to persist JWT secret' in str(e):
        logger.warning('falling back to env secret; sessions reset on restart')
        secret = os.environ['AUTH_JWT_SECRET']
    else:
        raise

Prevention

When it happens

Trigger: base_dir does not exist and cannot be created (read-only filesystem, missing parent, permission denied on DEER_FLOW_HOME); the directory is writable for mkdir but the file create/write is denied (immutable flag, disk full, quota exceeded); container running with a read-only volume for the config dir.

Common situations: Container image where the data path is read-only and no writable volume is mounted; disk-full conditions; a misconfigured DEER_FLOW_HOME pointing into the package install tree owned by root.

Related errors


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