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

  1. Client-side validate a non-blank URL before issuing the request
  2. 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

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


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/c131ea3948429a53. Report an issue: GitHub.