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

  1. Clear the stale browser state on the context and re-run browser.search to rebuild pages with url fields
  2. Pass an explicit url instead of the cursor for this open
  3. 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

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


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/c704325bff8b932a. Report an issue: GitHub.