{"record":{"id":"c704325bff8b932a","repo":"sgl-project/sglang","slug":"no-url-recorded-for-browser-cursor-cursor","errorCode":null,"errorMessage":"No URL recorded for browser cursor: {cursor}","messagePattern":"No URL recorded for browser cursor: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/entrypoints/tool.py","lineNumber":204,"sourceCode":"        )\n        return self._format_matches(pattern, searchable_text)\n\n    def _resolve_url(self, context: \"ConversationContext\", args: dict[str, Any]) -> str:\n        if args.get(\"url\"):\n            return str(args[\"url\"])\n\n        state = self._browser_state(context)\n        cursor = self._normalize_cursor(args.get(\"cursor\"))\n        if not cursor:\n            raise ValueError(\"browser.open requires a cursor or url\")\n\n        page = state[\"pages\"].get(cursor)\n        if page is None:\n            raise ValueError(f\"Unknown browser cursor: {cursor}\")\n\n        url = page.get(\"url\") or page.get(\"id\")\n        if not url:\n            raise ValueError(f\"No URL recorded for browser cursor: {cursor}\")\n        return str(url)\n\n    def _normalize_cursor(self, cursor: Any) -> str | None:\n        if cursor is None:\n            return None\n        cursor_str = str(cursor)\n        # GPT-OSS emits 0 as a 1-based cursor; map it to the first result.\n        if cursor_str == \"0\":\n            return \"1\"\n        return cursor_str\n\n    def _cursor_for_url(self, state: dict[str, Any], url: str) -> str | None:\n        for cursor, page in state[\"pages\"].items():\n            if page.get(\"url\") == url or page.get(\"id\") == url:\n                return cursor\n        return None\n\n    def _best_snippet(self, result: dict[str, Any]) -> str:","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/entrypoints/tool.py#L186-L222","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nargs = {\"cursor\": \"0\"}  # page entry has no url\n# after\nargs = {\"url\": \"https://sglang.ai\"}","handlingStrategy":"fallback","validationCode":"page = state[\"pages\"].get(str(args[\"cursor\"]))\nif not (page and (page.get(\"url\") or page.get(\"id\"))):\n    args = {\"url\": known_url}  # fall back to explicit url","typeGuard":"def page_has_url(page) -> bool:\n    return bool(page) and bool(page.get(\"url\") or page.get(\"id\"))","tryCatchPattern":"try:\n    url = tool._resolve_url(context, args)\nexcept ValueError as e:\n    if \"No URL recorded\" in str(e):\n        url = explicit_url  # fallback","preventionTips":["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"],"tags":["browser","state-corruption","url","tool-calling"],"backgroundTag":"stale-tool-state","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}