makeplane/plane · error · AuthenticationException
5017
5017
Error message
BOT_USER_LOGIN_FORBIDDEN
What it means
Thrown at base.py:340 when the resolved user has is_bot=True. Bot identities (e.g. the WORKSPACE_SEED bot) are internal service accounts that act only through API tokens and must never be assumable via email/password, magic code, or OAuth. Because bots are provisioned internally and never created through the signup path, guarding only on an existing user record is sufficient.
Source
Thrown at apps/api/plane/authentication/adapter/base.py:340
# provisioned account that was never deactivated has last_logout_time=None
# and is allowed through for its first login; an account deactivated via
# the API has last_logout_time set and is blocked regardless of whether
# it had previously logged in.
if user and not user.is_active and user.last_logout_time is not None:
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["USER_ACCOUNT_DEACTIVATED"],
error_message="USER_ACCOUNT_DEACTIVATED",
payload={"email": email},
)
# Reject bot service accounts (BOT_USER_LOGIN_FORBIDDEN). Bots (is_bot=True,
# e.g. the WORKSPACE_SEED bot) are internal identities that act only through
# API tokens; they must never be assumable via the interactive login/signup
# flow (email/password, magic code, or any OAuth provider). A brand-new
# signup can never be a bot — bots are provisioned internally, never through
# this path — so guarding on an existing `user` record is sufficient.
if user and user.is_bot:
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["BOT_USER_LOGIN_FORBIDDEN"],
error_message="BOT_USER_LOGIN_FORBIDDEN",
payload={"email": email},
)
# True = new user (signup), False = returning user (login)
is_signup = not bool(user)
# If user is not present, create a new user
if not user:
# New user
self.__check_signup(email)
# Initialize user
user = User(email=email, username=uuid.uuid4().hex)
# Check if password is autoset
if self.user_data.get("user").get("is_password_autoset"):
user.set_password(uuid.uuid4().hex)View on GitHub (pinned to 1c8a60f858)
Solutions
- Do not log in as a bot interactively; generate and use an API token for that bot identity instead.
- If a human must own that email, create a separate non-bot User account with a different email and migrate ownership.
- Confirm the target account is actually a bot (User.is_bot) before assuming misuse.
Example fix
# before: trying to sign in as bot@workspace via UI -> 5017 # after: use an API token for the bot curl -H "X-API-Key: <bot-api-token>" https://plane.example.com/api/...
Defensive patterns
Strategy: validation
Validate before calling
from plane.db.models import User
def is_interactive_login_allowed(email: str) -> bool:
u = User.objects.filter(email=email).first()
return u is None or not u.is_bot # None = new signup, allowed Type guard
def is_bot_identity(user) -> bool:
return user is not None and bool(user.is_bot) Try / catch
try:
adapter_login(email, password)
except AuthenticationException as e:
if e.error_code == 5017:
suggest_api_token_for(email=e.payload.get('email'))
else:
raise Prevention
- Never reuse a bot identity's email for human login.
- Provision API tokens for automation instead of trying to log in interactively.
When it happens
Trigger: Any interactive login/signup where the email resolves to an existing User with is_bot=True raises AuthenticationException code 5017 with payload {email}. Hit when someone tries to sign in with the email address that a bot service account was registered under.
Common situations: An admin or script attempts to log in as the WORKSPACE_SEED bot or another bot user via the web UI or /auth/ endpoints. Also occurs if a bot's email was reused for a human account elsewhere and the human tries the wrong identity.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/dece209b769e308d.
Report an issue: GitHub.