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
- 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.
- Point DEER_FLOW_HOME at a writable location (per-host persistent path) so the secret survives restarts.
- As a fallback, set AUTH_JWT_SECRET env var explicitly so persistence is not required.
- 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
- Mount a writable persistent volume at DEER_FLOW_HOME in Docker/K8s.
- Prefer an explicit AUTH_JWT_SECRET from a secret manager for multi-replica deployments — file persistence is single-host only.
- Check disk space and read-only mount flags in deployment checklists.
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
- Failed to read JWT secret from {secret_file}. Set AUTH_JWT_S
- Permission denied: {resource}:{action}
- Token error: {payload.value}
- USER_NOT_FOUND
- TOKEN_INVALID
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/c717a60686dae795.
Report an issue: GitHub.