{"record":{"id":"bee1901a487874c5","repo":"bytedance/deer-flow","slug":"failed-to-read-jwt-secret-from-secret-file-set","errorCode":null,"errorMessage":"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.","messagePattern":"Failed to read JWT secret from (.+?)\\. Set AUTH_JWT_SECRET explicitly or fix DEER_FLOW_HOME/base directory permissions so DeerFlow can read its persisted auth secret\\.","errorType":"console","errorClass":"RuntimeError","httpStatus":null,"severity":"critical","filePath":"backend/app/gateway/auth/config.py","lineNumber":48,"sourceCode":"\n\n_auth_config: AuthConfig | None = None\n\n\ndef _load_or_create_secret() -> str:\n    \"\"\"Load persisted JWT secret from ``{base_dir}/.jwt_secret``, or generate and persist a new one.\"\"\"\n    from deerflow.config.paths import get_paths\n\n    paths = get_paths()\n    secret_file = paths.base_dir / _SECRET_FILE\n\n    try:\n        if secret_file.exists():\n            secret = secret_file.read_text(encoding=\"utf-8\").strip()\n            if secret:\n                return secret\n    except OSError as exc:\n        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\n\n    secret = secrets.token_urlsafe(32)\n    try:\n        secret_file.parent.mkdir(parents=True, exist_ok=True)\n        fd = os.open(secret_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)\n        with os.fdopen(fd, \"w\", encoding=\"utf-8\") as fh:\n            fh.write(secret)\n    except OSError as exc:\n        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\n    return secret\n\n\ndef get_auth_config() -> AuthConfig:\n    \"\"\"Get the global AuthConfig instance. Parses from env on first call.\"\"\"\n    global _auth_config\n    if _auth_config is None:\n        from dotenv import load_dotenv\n","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/auth/config.py#L30-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Alternatively set AUTH_JWT_SECRET in the environment to a stable value and skip the file path entirely.","If on NFS/SELinux, verify the mount/options permit the service user's reads.","Note: an empty-but-readable file falls through to regeneration (a different error, 79, covers write failure)."],"exampleFix":"# before: data dir owned by root, gateway runs as deerflow\ndocker compose up   # -> Failed to read JWT secret ...\n\n# after\nsudo chown -R 1000:1000 ./deer-flow-data\ndocker compose up","handlingStrategy":"try-catch","validationCode":"from pathlib import Path\n\ndef jwt_secret_readable(base_dir: Path) -> bool:\n    f = base_dir / '.jwt_secret'\n    if not f.exists():\n        return True  # nothing to read yet\n    try:\n        return bool(f.read_text(encoding='utf-8').strip())\n    except OSError:\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    secret = load_jwt_secret()\nexcept RuntimeError as e:\n    if 'Failed to read JWT secret' in str(e):\n        secret = os.environ['AUTH_JWT_SECRET']  # documented escape hatch\n    else:\n        raise","preventionTips":["Run the Gateway as a dedicated user that owns DEER_FLOW_HOME from first boot.","Set AUTH_JWT_SECRET explicitly in containerized/orchestrated deployments.","Smoke-test secret readability in the container entrypoint before exec'ing the app."],"tags":["auth","jwt","permissions","deployment"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}