bytedance/deer-flow · error · HTTPException
TOKEN_INVALID
TOKEN_INVALID
Error message
Token revoked (password changed)
What it means
HTTP 401 with code TOKEN_INVALID: the token's ver (token_version) claim does not match user.token_version in the store. DeerFlow increments a user's token_version on password change, which retroactively revokes all previously issued JWTs; a mismatch means this token predates the password change.
Source
Thrown at backend/app/gateway/deps.py:777
payload = decode_token(access_token)
if isinstance(payload, TokenError):
raise HTTPException(
status_code=401,
detail=AuthErrorResponse(code=token_error_to_code(payload), message=f"Token error: {payload.value}").model_dump(),
)
provider = get_local_provider()
user = await provider.get_user(payload.sub)
if user is None:
raise HTTPException(
status_code=401,
detail=AuthErrorResponse(code=AuthErrorCode.USER_NOT_FOUND, message="User not found").model_dump(),
)
# Token version mismatch → password was changed, token is stale
if user.token_version != payload.ver:
raise HTTPException(
status_code=401,
detail=AuthErrorResponse(code=AuthErrorCode.TOKEN_INVALID, message="Token revoked (password changed)").model_dump(),
)
return user
async def require_admin_user(request: Request, *, detail: str) -> None:
"""Require the authenticated caller to be an admin user.
``AuthMiddleware`` normally stamps ``request.state.user`` before the request
reaches a router. Falling back to the strict dependency keeps the route safe
in tests or alternative ASGI compositions that mount a router without the
global middleware. ``detail`` is the route-specific 403 message.
Centralising this here means a future change to the admin definition (e.g.
allowing an internal system role, adding audit logging, or switching to a
permission-based check) lands in one place instead of drifting across theView on GitHub (pinned to 1dd6ba1acb)
Solutions
- Re-login on the affected client — the new token carries the current token_version
- If this hits every client after a routine deploy (not a password change), check whether something is incorrectly bumping token_version (e.g. a user-update code path writing the field unconditionally)
- In tests, re-mint tokens after any user password/version mutation
Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
null
Try / catch
if (e.status === 401 && e.detail?.code === 'TOKEN_INVALID') {
showNotice('Password changed elsewhere — please sign in again');
logout();
} Prevention
- Handle TOKEN_INVALID as 're-login required', never as a retryable error
- In multi-device setups, expect 401 storms on other devices after any password change
- Tests: re-mint tokens after exercising password-change endpoints
When it happens
Trigger: Authenticated call using a token issued before the most recent password change for that user — e.g. an old tab, a second device, or a saved session that kept the pre-rotation cookie.
Common situations: User changed password on device A; device B still sends the old cookie; password reset flow bumped token_version; automated tests reusing fixtures with stale ver claims after a password-update step.
Related errors
- Token error: {payload.value}
- USER_NOT_FOUND
- Invalid token
- Token revoked (password changed)
- Failed to read JWT secret from {secret_file}. Set AUTH_JWT_S
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/1bef0ff87eed6311.
Report an issue: GitHub.