bytedance/deer-flow · warning · HTTPException
URL is required
Error message
URL is required
What it means
400 Bad Request from the browser navigate route (browser.py:97): the request body's url field is empty after stripping whitespace. BrowserNavigateRequest.url is required by the schema, but an all-whitespace string (' ') passes schema presence checks and only fails this explicit trimmed-empty check in the handler.
Source
Thrown at backend/app/gateway/routers/browser.py:97
)
@require_permission("threads", "write", owner_check=True, require_existing=True)
async def navigate_browser(thread_id: ThreadId, body: BrowserNavigateRequest, request: Request) -> BrowserNavigateResponse:
user_id = str(request.state.auth.user.id)
thread_store = getattr(request.app.state, "thread_store", None)
if thread_store is None or not await _browser_thread_owned_by(thread_store, thread_id, user_id):
raise HTTPException(status_code=404, detail=f"Thread {thread_id} not found")
if not _browser_tools_enabled():
raise HTTPException(status_code=404, detail="Browser automation is not enabled")
try:
from deerflow.community.browser_automation import navigate_and_capture, redact_browser_url
except ImportError as exc: # Playwright is an optional dependency.
raise HTTPException(status_code=501, detail="Browser automation is not available") from exc
url = body.url.strip()
if not url:
raise HTTPException(status_code=400, detail="URL is required")
outputs_path = get_paths().sandbox_outputs_dir(thread_id, user_id=get_effective_user_id())
try:
result = await navigate_and_capture(thread_id=thread_id, url=url, outputs_path=outputs_path)
except ValueError as exc:
# SSRF / URL validation failure.
raise HTTPException(status_code=400, detail=str(exc)) from exc
except Exception as exc:
logger.error(
"Browser navigate failed: thread_id=%s url=%s err_type=%s",
thread_id,
redact_browser_url(url),
type(exc).__name__,
)
raise HTTPException(status_code=502, detail="Browser navigation failed") from exc
return BrowserNavigateResponse(**result)
View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Client-side validate a non-blank URL before issuing the request
- Populate the url field with a complete absolute URL (scheme + host), since downstream navigate_and_capture performs SSRF validation expecting a real URL
Example fix
// before
await fetch(`/api/threads/${id}/browser/navigate`, {
method: "POST",
body: JSON.stringify({ url: this.urlInput }), // may be ""
});
// after
const url = this.urlInput.trim();
if (!url) return;
await fetch(`/api/threads/${id}/browser/navigate`, {
method: "POST",
body: JSON.stringify({ url }),
}); Defensive patterns
Strategy: validation
Validate before calling
const url = rawUrl.trim();
if (!url) { setFormError("URL is required"); return; }
await postNavigate(threadId, url); Type guard
const isNonBlankUrl = (u: unknown): u is string => typeof u === "string" && u.trim().length > 0;
Try / catch
try { await navigateBrowser(threadId, url) } catch (e) { if (e.status === 400 && e.detail === "URL is required") setFormError("Enter a URL"); else throw e; } Prevention
- Trim and require non-empty input in the UI before enabling the submit action
- Send complete absolute URLs (scheme+host) so downstream SSRF validation also passes
- Unit-test the form with whitespace-only input
When it happens
Trigger: POST /api/threads/{id}/browser/navigate with body {"url": ""} or {"url": " "} — an empty string is rejected by Pydantic as 422, but whitespace-only reaches the handler and yields this 400.
Common situations: Frontend sending an unvalidated input value; automation script passing an unset variable (url="" after a failed shell interpolation).
Related errors
- {e}
- Invalid provider ID
- Missing code or state parameter
- Input text is required
- Input text exceeds {max_chars} characters
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/c131ea3948429a53.
Report an issue: GitHub.