sgl-project/sglang · error · ValueError

Unknown browser cursor: {cursor}

Error message

Unknown browser cursor: {cursor}

What it means

SGLang's browser resolver was given a cursor (e.g. a search-result index like '0' or '1-3'), but the per-context browser state dict has no page entry for it. Cursors only exist after a search/open result recorded them on the conversation context.

Source

Thrown at python/sglang/srt/entrypoints/tool.py:200

            return self._format_matches(pattern, text)

        searchable_text = "\n\n".join(
            self._best_snippet(page) for page in state["pages"].values()
        )
        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:

View on GitHub (pinned to 0132848349)

Solutions

  1. Re-run browser.search to (re)populate cursors before referencing them
  2. Use a cursor that actually appears in the last formatted search results
  3. Persist/reattach the browser state on the context across turns if needed

Example fix

# before
args = {"cursor": "7"}  # only 3 results existed
# after
args = {"cursor": "2"}
Defensive patterns

Strategy: validation

Validate before calling

state = getattr(context, "_sglang_exa_browser_state", {"pages": {}})
cursor = str(args["cursor"])
assert cursor in state["pages"], f"unknown cursor {cursor}"

Type guard

def cursor_exists(context, cursor: str) -> bool:
    pages = getattr(context, "_sglang_exa_browser_state", {}).get("pages", {})
    return str(cursor) in pages

Try / catch

try:
    url = tool._resolve_url(context, args)
except ValueError as e:
    if "Unknown browser cursor" in str(e):
        await tool._dispatch_browser_call(context, "browser.search", {"query": ...})
        url = tool._resolve_url(context, args)

Prevention

When it happens

Trigger: browser.open or browser.find with {"cursor": "5"} when only cursors 0-2 were recorded in context._sglang_exa_browser_state['pages'], or a cursor from a different/earlier conversation context.

Common situations: Model hallucinating a cursor beyond the result list, reusing a cursor after context state was reset, or a new context object lacking prior state (e.g. multi-turn session where _sglang_exa_browser_state was not persisted).

Related errors


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