sgl-project/sglang · error · ValueError
No URL recorded for browser cursor: {cursor}
Error message
No URL recorded for browser cursor: {cursor} What it means
SGLang's browser resolver found the cursor's page entry, but that entry contains neither a 'url' nor an 'id' field, so there is nothing to open. This is a corrupted or incomplete page record in the per-context browser state rather than a bad cursor.
Source
Thrown at python/sglang/srt/entrypoints/tool.py:204
)
return self._format_matches(pattern, searchable_text)
def _resolve_url(self, context: "ConversationContext", args: dict[str, Any]) -> str:
if args.get("url"):
return str(args["url"])
state = self._browser_state(context)
cursor = self._normalize_cursor(args.get("cursor"))
if not cursor:
raise ValueError("browser.open requires a cursor or url")
page = state["pages"].get(cursor)
if page is None:
raise ValueError(f"Unknown browser cursor: {cursor}")
url = page.get("url") or page.get("id")
if not url:
raise ValueError(f"No URL recorded for browser cursor: {cursor}")
return str(url)
def _normalize_cursor(self, cursor: Any) -> str | None:
if cursor is None:
return None
cursor_str = str(cursor)
# GPT-OSS emits 0 as a 1-based cursor; map it to the first result.
if cursor_str == "0":
return "1"
return cursor_str
def _cursor_for_url(self, state: dict[str, Any], url: str) -> str | None:
for cursor, page in state["pages"].items():
if page.get("url") == url or page.get("id") == url:
return cursor
return None
def _best_snippet(self, result: dict[str, Any]) -> str:View on GitHub (pinned to 0132848349)
Solutions
- Clear the stale browser state on the context and re-run browser.search to rebuild pages with url fields
- Pass an explicit url instead of the cursor for this open
- Check for upstream API/response-format changes if this recurs across cursors
Example fix
# before
args = {"cursor": "0"} # page entry has no url
# after
args = {"url": "https://sglang.ai"} Defensive patterns
Strategy: fallback
Validate before calling
page = state["pages"].get(str(args["cursor"]))
if not (page and (page.get("url") or page.get("id"))):
args = {"url": known_url} # fall back to explicit url Type guard
def page_has_url(page) -> bool:
return bool(page) and bool(page.get("url") or page.get("id")) Try / catch
try:
url = tool._resolve_url(context, args)
except ValueError as e:
if "No URL recorded" in str(e):
url = explicit_url # fallback Prevention
- Never mutate _sglang_exa_browser_state by hand; let search/open populate it
- If cursors repeatedly lack urls, clear state and re-search to rebuild it
When it happens
Trigger: state['pages'][cursor] exists but is {}, {'title': ...} with no url/id — typically from a malformed search/contents response stored by _format_search_results, or manual injection of page state.
Common situations: Upstream Exa API response shape changed so url/id fields go missing when results are cached, or user code mutating _sglang_exa_browser_state directly.
Related errors
- No browser tool call found
- browser.search requires a query
- browser.find requires a pattern
- Unknown browser action: {recipient}
- browser.open requires a cursor or url
AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28).
Data as JSON: /api/errors/c704325bff8b932a.
Report an issue: GitHub.