odysseus-dev/odysseus · warning · HTTPException
Workspace browsing is admin-only
Error message
Workspace browsing is admin-only
What it means
HTTP 403 from GET /browse (workspace routes): the authenticated user is neither an admin nor the single user of a single-user deployment, so filesystem browsing is denied. The endpoint enumerates server directories (same trust level as read_file/write_file/bash tools, which are in NON_ADMIN_BLOCKED_TOOLS), so any non-admin is blocked from mapping the host directory tree.
Source
Thrown at routes/workspace_routes.py:29
_MAX_BROWSE_DIRS = 500
def setup_workspace_routes():
router = APIRouter(prefix="/api/workspace", tags=["workspace"])
@router.get("/browse")
def browse(request: Request, path: str = Query(default="")):
"""List subdirectories of `path` (default: home) so the UI can navigate
the server filesystem and pick a workspace folder. Directories only.
ADMIN-ONLY: this enumerates the server filesystem, so it is gated the
same way the file/shell tools are (read_file/write_file/bash are in
NON_ADMIN_BLOCKED_TOOLS). A non-admin who can't use those tools must not
be able to map the host's directory tree either.
"""
owner = get_current_user(request)
if not owner_is_admin_or_single_user(owner):
raise HTTPException(status_code=403, detail="Workspace browsing is admin-only")
# Resolve symlinks so the reported path is canonical and the UI navigates
# real directories (defends against symlink games in displayed paths).
target = os.path.realpath(os.path.expanduser(path.strip() or "~"))
if not os.path.isdir(target):
target = os.path.realpath(os.path.expanduser("~"))
dirs = []
try:
with os.scandir(target) as it:
for entry in it:
try:
# Don't follow symlinks when classifying - a symlinked
# dir is skipped rather than letting the browser wander
# off via a link. Hidden entries are omitted.
if entry.is_dir(follow_symlinks=False) and not entry.name.startswith("."):
# Build the child path server-side with os.path.join
# so it's correct on Windows (backslashes) and Linux.View on GitHub (pinned to f9235ebbf1)
Solutions
- Log in as an admin user (or the single user in single-user mode) to browse workspaces
- Have an admin grant the user admin rights if browsing is required for their role
- Non-admins: ask an admin to set the workspace path, or use per-user workspace settings if available
Defensive patterns
Strategy: type-guard
Validate before calling
user = get_current_user(request)
if not owner_is_admin_or_single_user(user):
hide_workspace_picker() # never call /browse Type guard
def can_browse_workspace(user) -> bool:
return owner_is_admin_or_single_user(user) Try / catch
if resp.status_code == 403 and 'admin-only' in detail:
hide_picker_and_show_notice('Ask an admin to set the workspace') Prevention
- Gate the workspace picker UI on the user's admin flag, not just on page load
- Re-check role after permission changes / re-login
When it happens
Trigger: A regular multi-user account calling /browse; a user whose admin flag was revoked but whose browser tab still has the workspace picker open; anonymous/session-cookie access without admin role.
Common situations: Multi-user installs where the workspace UI is visible to everyone but only functional for admins; permission changes not reflected until re-login.
Related errors
- Workspace selection is admin-only
- Admin only
- API token missing required scope: {required}
- API token missing required scope: {' and '.join(sorted(missi
- API token owner mismatch
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/148ee9b0fea088ce.
Report an issue: GitHub.