{"record":{"id":"60c74c36a781d6a2","repo":"open-webui/open-webui","slug":"terminal-server-url-is-required","errorCode":null,"errorMessage":"Terminal server URL is required","messagePattern":"Terminal server URL is required","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"backend/open_webui/routers/configs.py","lineNumber":361,"sourceCode":"        subject_type='config',\n        data={'count': len(connections)},\n    )\n    return {'TERMINAL_SERVER_CONNECTIONS': connections}\n\n\n@router.post('/terminal_servers/verify')\nasync def verify_terminal_server_connection(\n    request: Request, form_data: TerminalServerConnection, user=Depends(get_admin_user)\n):\n    \"\"\"\n    Verify the connection to a terminal server by detecting its type.\n\n    Tries GET {url}/api/v1/policies (orchestrator) then GET {url}/api/config\n    (plain terminal).  Returns ``{status: true, type: \"orchestrator\"|\"terminal\"}``.\n    \"\"\"\n    base_url = (form_data.url or '').rstrip('/')\n    if not base_url:\n        raise HTTPException(status_code=400, detail='Terminal server URL is required')\n\n    headers = {}\n    if form_data.auth_type == 'bearer' and form_data.key:\n        headers.update(bearer_auth_header(form_data.key))\n\n    try:\n        async with aiohttp.ClientSession(\n            trust_env=True,\n            timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT),\n        ) as session:\n            # Orchestrators expose a policies API; plain terminals don't.\n            try:\n                async with session.get(\n                    f'{base_url}/api/v1/policies', headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL\n                ) as resp:\n                    if resp.ok:\n                        return {'status': True, 'type': 'orchestrator'}\n            except Exception:","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/open-webui/open-webui/blob/01f4282f1ffe0d6212f58d3afbeae21fffd0c4be/backend/open_webui/routers/configs.py#L343-L379","documentation":"POST /terminal_servers/verify (admin-only) strips the submitted url and rejects an empty string with 400 'Terminal server URL is required'. Pure input validation before any network call is attempted.","triggerScenarios":"Submitting the verify form with url: '', url: null coerced after strip, or a url consisting only of whitespace/slashes (rstrip('/') reduces it to '').","commonSituations":"Admin UI form submitted before the URL field was filled; config automation passing an unset environment-derived value that arrives as empty string.","solutions":["Provide a non-empty base URL, e.g. 'https://terminal.example.com' (no trailing path required; trailing slash is trimmed).","Client-side: require the field before enabling submit.","If the value comes from config/env, default it or fail the pipeline earlier with a clear message."],"exampleFix":"// before\nawait api.post('/configs/terminal_servers/verify', { url: '' });\n\n// after\nif (!url.trim()) throw new Error('Terminal server URL is required');\nawait api.post('/configs/terminal_servers/verify', { url: url.trim() });","handlingStrategy":"validation","validationCode":"if (!form.url || !form.url.trim()) throw new Error('Terminal server URL is required');","typeGuard":"function isNonEmptyUrl(u: unknown): u is string { return typeof u === 'string' && u.trim().length > 0 && u.trim() !== '/'; }","tryCatchPattern":null,"preventionTips":["Require the URL field client-side before submit","Trim inputs; remember '/'+ trailing slashes collapse to empty after rstrip"],"tags":["open-webui","terminal-server","http-400","validation"],"backgroundTag":null,"analyzedSha":"01f4282f1ffe0d6212f58d3afbeae21fffd0c4be","analyzedAt":"2026-08-14T18:25:22.715Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}