{"id":26175,"date":"2026-09-30T04:41:40","date_gmt":"2026-09-30T04:41:40","guid":{"rendered":"https:\/\/capskip.com\/?p=26175"},"modified":"2026-09-30T04:41:40","modified_gmt":"2026-09-30T04:41:40","slug":"browser-use-captcha","status":"publish","type":"post","link":"https:\/\/capskip.com\/zh\/browser-use-captcha\/","title":{"rendered":"\u5982\u4f55\u5728 Browser Use \u4e2d\u7528\u81ea\u5b9a\u4e49\u5de5\u5177\u8bc6\u522b\u9a8c\u8bc1\u7801"},"content":{"rendered":"<p>To solve CAPTCHAs in Browser Use on your own browser, register a custom tool that reads the widget&#8217;s sitekey from the page, asks CapSkip for a token and writes it into the form, then tell the agent in its system message that the tool exists. That covers Browser Use CAPTCHA handling on a local Chromium, apart from two limits that both default to 180 seconds and need raising, because a reCAPTCHA solve can run longer than either. The system message part is not optional. Browser Use&#8217;s default system prompt tells the model that CAPTCHAs are solved automatically, which is true on Browser Use&#8217;s cloud browsers and false on a local Chromium, so without it the agent waits for a solve that never comes. This guide builds the Browser Use CAPTCHA tool step by step, checked against Browser Use 0.13.10.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">What you need<\/h2>\n<ul>\n<li>CapSkip running on a Windows machine. The tool below handles reCAPTCHA v2, including invisible and Enterprise widgets, and the Cloudflare Turnstile widget.<\/li>\n<li>Python 3.11 or later, which Browser Use requires, with browser-use and version 1.3.0 or later of the capskip package. Browser Use drives a Chrome or Chromium it finds on the machine.<\/li>\n<li>An API key for whichever chat model you give the agent; for Claude, set it as ANTHROPIC_API_KEY. The samples use Claude Opus 5.5 through ChatAnthropic with adaptive thinking switched on, and that argument matters: without it, Browser Use 0.13.10 forces the model&#8217;s tool choice, which Claude Opus 5.5 and Sonnet 5.5 reject, so every step fails.<\/li>\n<li>An address for the solver. In Local mode CapSkip answers on 127.0.0.1 for that device only; in Server mode it listens on your network address or public IP, so an agent on another machine can call it over the API. Both are set under <a href=\"https:\/\/capskip.com\/setup-guide\/#connection-settings\">connection settings<\/a>.<\/li>\n<\/ul>\n<div data-no-translation>\n<pre data-enlighter-language=\"bash\" class=\"EnlighterJSRAW\"># Quoted, so cmd.exe does not read &gt;= as a redirect\npip install browser-use &quot;capskip&gt;=1.3.0&quot;<\/pre>\n<\/div>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Why does Browser Use ignore CAPTCHAs on a local browser?<\/h2>\n<p>Because it has been told to. The browser rules in the system prompt Browser Use uses with its default settings include the line &quot;CAPTCHAs are automatically solved by the browser&quot;, followed by an instruction not to try solving them by hand. That describes Browser Use&#8217;s cloud browsers, which solve CAPTCHAs in their own proxy and announce it to the library. The library has a matching watchdog, switched on by the captcha_solver setting on BrowserProfile (on by default), which pauses the agent while a cloud browser solves. It listens only for events those cloud browsers send.<\/p>\n<p>On a Chromium on your own machine no such event ever arrives. The agent sees the widget, believes the rules, and waits, scrolls or tries another route until it runs out of steps. When a run ends that way, Browser Use suggests switching to its cloud browsers. The alternative, if you want the browser on your own hardware, is to give the agent a tool that really solves the widget and to correct the rule it was given.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Step 1: register a solve_captcha tool<\/h2>\n<p>Custom tools are async functions registered with the tools.action decorator on a Tools instance. Browser Use injects special parameters by name: browser_session gives you the live session and page_url the address of the current page. The tool needs two small pieces of JavaScript, one to find the widget and one to fill in the token.<\/p>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">FIND = &quot;&quot;&quot;() =&gt; {\n  const w = document.querySelector(\n    '.g-recaptcha[data-sitekey], .cf-turnstile[data-sitekey]');\n  if (!w) return null;\n  return {\n    kind: w.classList.contains('cf-turnstile') ? 'turnstile' : 'recaptcha',\n    sitekey: w.dataset.sitekey,\n    invisible: w.dataset.size === 'invisible' || w.tagName !== 'DIV',\n    enterprise: !!document.querySelector(\n      'script[src*=&quot;recaptcha\/enterprise.js&quot;]'),\n    callback: w.dataset.callback || null,\n  };\n}&quot;&quot;&quot;\n\nFILL = &quot;&quot;&quot;(kind, token, callback) =&gt; {\n  const name = kind === 'turnstile' ? 'cf-turnstile-response'\n                                    : 'g-recaptcha-response';\n  const fields = document.querySelectorAll(`[name=&quot;${name}&quot;]`);\n  fields.forEach(f =&gt; { f.value = token; });\n  if (callback &amp;&amp; typeof window[callback] === 'function') {\n    window[callback](token);\n  }\n  return fields.length;\n}&quot;&quot;&quot;<\/pre>\n<\/div>\n<p>Both are written as arrow functions, because the page object&#8217;s evaluate method refuses anything else. It passes extra arguments as JSON, and it always returns a string: an object comes back JSON-encoded, a number as its digits, and null as an empty string. FILL writes the token into the response field the widget created and calls the function named in data-callback if the widget declares one, since some forms wait for that callback instead of reading the field.<\/p>\n<p>FIND also reads two things the solve depends on. A widget bound to a button is invisible even without a data-size attribute, so any g-recaptcha element that is not a div counts as invisible. And a page that loads Google&#8217;s enterprise.js script is solved as reCAPTCHA Enterprise, since Enterprise widgets use the same markup as the standard ones.<\/p>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">@tools.action(\n    &quot;Solve the reCAPTCHA v2 or Cloudflare Turnstile widget on the current &quot;\n    &quot;page. Call it after the other fields are filled, then submit the form &quot;\n    &quot;unless the page has already moved on.&quot;\n)\nasync def solve_captcha(browser_session: BrowserSession, page_url: str):\n    page = await browser_session.must_get_current_page()\n    raw = await page.evaluate(FIND)\n    widget = json.loads(raw) if raw else None\n    if not widget:\n        return ActionResult(error=&quot;No reCAPTCHA or Turnstile widget found.&quot;)\n\n    try:\n        if widget[&quot;kind&quot;] == &quot;turnstile&quot;:\n            result = await solver.turnstile(widget[&quot;sitekey&quot;], page_url)\n        else:\n            extra = {&quot;invisible&quot;: 1} if widget[&quot;invisible&quot;] else {}\n            result = await solver.recaptcha(\n                widget[&quot;sitekey&quot;], page_url,\n                enterprise=int(widget[&quot;enterprise&quot;]), **extra)\n    except CapSkipError as exc:\n        return ActionResult(error=f&quot;CapSkip did not solve it: {exc!r}&quot;)\n\n    filled = await page.evaluate(\n        FILL, widget[&quot;kind&quot;], result[&quot;code&quot;], widget[&quot;callback&quot;])\n    if filled == &quot;0&quot;:\n        return ActionResult(error=&quot;Solved, but no response field to fill.&quot;)\n    return ActionResult(\n        extracted_content=f&quot;Solved the {widget['kind']} CAPTCHA on {page_url} &quot;\n                          &quot;and filled in the token. Submit the form now, &quot;\n                          &quot;unless the page has already moved on.&quot;,\n    )<\/pre>\n<\/div>\n<p>The description string is what the model reads when it decides which action to take, so it says when to call the tool as well as what it does. The success message goes in extracted_content alone. When an ActionResult also sets long_term_memory, Browser Use shows the model the memory and drops the content, so an instruction such as &quot;Submit the form now&quot; placed in the content never arrives. Every failure comes back as an ActionResult with an error the model can read and react to, rather than an exception.<\/p>\n<p>Use AsyncCapSkip here, not the synchronous CapSkip client. Browser Use runs the browser connection on the same event loop as your tool, and a blocking solve would freeze all of it for as long as the solve takes. In Python, AsyncCapSkip is a genuine asyncio client rather than an alias.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Step 2: tell the agent the tool exists<\/h2>\n<p>Pass the tools to the agent and add one paragraph to its system message. extend_system_message is appended after the default prompt, so it comes after the rule it has to correct.<\/p>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">EXTRA = (\n    &quot;This browser does not solve CAPTCHAs on its own, whatever the rules &quot;\n    &quot;above say. When a page shows a reCAPTCHA or Turnstile widget, fill in &quot;\n    &quot;the other fields, call solve_captcha, then submit the form unless the &quot;\n    &quot;page has already moved on. Never click the CAPTCHA checkbox or &quot;\n    &quot;challenge yourself.&quot;\n)\n\nagent = Agent(\n    task=&quot;Sign up at https:\/\/example.com\/signup with YOUR_EMAIL.&quot;,\n    llm=ChatAnthropic(model=&quot;claude-opus-5-5&quot;, thinking={&quot;type&quot;: &quot;adaptive&quot;}),\n    tools=tools,\n    extend_system_message=EXTRA,\n    step_timeout=420,\n)<\/pre>\n<\/div>\n<p>The order in that paragraph matters. A reCAPTCHA token is only valid for about two minutes, as <a href=\"https:\/\/capskip.com\/recaptcha-token-expiration\/\">the guide to reCAPTCHA token expiration<\/a> explains, and each model step takes seconds. Solving first and then filling in a long form can let the token expire before the submit, so the agent is told to solve last. A model can do all three in a single step, typing, solving and clicking submit, and Browser Use runs the actions of a step in order. The clause about the page moving on is for invisible widgets. Their callback usually submits the form itself, so by the time FILL has called it the page has already gone on, and a second click would only fail.<\/p>\n<p>The thinking argument on ChatAnthropic is not decoration. Browser Use asks Claude for its next action through a tool call, and in version 0.13.10, without thinking, it forces that tool choice. Claude Opus 5.5 and Sonnet 5.5 refuse a forced tool choice with a 400, so the agent would fail every step. With adaptive thinking on, Browser Use lets the model choose the tool, and the request goes through.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Step 3: raise the two 180 second limits<\/h2>\n<p>A Browser Use CAPTCHA solve has to fit inside two separate clocks, and both default to 180 seconds.<\/p>\n<ul>\n<li><strong>The per-action cap.<\/strong> Every action is wrapped in a timeout read from the BROWSER_USE_ACTION_TIMEOUT_S environment variable, and the agent never passes a value of its own. It is read once, when Browser Use first loads its tools module, which any import of Agent or Tools does, so setting it after that has no effect.<\/li>\n<li><strong>The step timeout.<\/strong> Agent&#8217;s step_timeout covers a whole step: preparing the page state, the model call, which Browser Use itself allows up to 90 seconds depending on the model, and every action in the step.<\/li>\n<\/ul>\n<p>On the other side, the SDK polls a reCAPTCHA for up to 300 seconds (recaptchaTimeout), and CapSkip&#8217;s own reCAPTCHA settings allow a task up to 250 seconds to wait for a thread and 250 more to solve. Most solves finish far sooner, but a queue or a slow proxy can take one past 180. With both limits at their defaults, the step timeout fires first, because the step started before the action did, and the agent reports that the step timed out after 180 seconds. Raise only step_timeout and the action cap takes over: it cancels the solve mid-poll and reports that the browser may be unresponsive, naming a dead CDP WebSocket. That message is misleading here. The browser is fine; the solve simply outlived the cap. Set both limits above the SDK&#8217;s.<\/p>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">import os\n\n# Before anything imports Agent or Tools from browser_use.\nos.environ.setdefault(&quot;BROWSER_USE_ACTION_TIMEOUT_S&quot;, &quot;330&quot;)\n\nfrom browser_use import Agent  # noqa: E402\n\n# Up to 300 s of SDK polling (a few more for the last poll), one\n# model call (90 s for Claude) and the page state, with room to spare.\nagent = Agent(task=&quot;...&quot;, llm=llm, tools=tools,\n              extend_system_message=EXTRA, step_timeout=420)<\/pre>\n<\/div>\n<p>A timed-out step also counts as one of the agent&#8217;s consecutive failures, and five in a row stop the agent, so a limit that is too tight costs more than one retry.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Running CapSkip on a server<\/h2>\n<p>The tool runs in your Python process, next to the agent. What matters is where that process runs, not where the browser is: even when the session&#8217;s cdp_url points at a Chrome on another machine, the solve still travels from your script to CapSkip. While the script and CapSkip share a Windows PC, 127.0.0.1 is right. When the agent moves to a VPS, a CI runner or a scheduled job on another box, loopback points at the wrong machine and the tool returns a NetworkException as its error. Switch CapSkip to Server mode and it listens on your network address or public IP, so the agent can call it over the same API. Use a static public IP when the route crosses the internet, turn on API key validation, and restrict the port with a Windows Firewall rule. The solver stays on your own Windows machine, and solves stay unmetered however many CAPTCHAs the agent meets.<\/p>\n<p>The SDK does not read environment variables by itself. Read CAPSKIP_HOST, CAPSKIP_PORT and CAPSKIP_API_KEY in your code and pass them to AsyncCapSkip, as the full example does.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Full working example<\/h2>\n<div data-no-translation>\n<pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\"># pip install browser-use &quot;capskip&gt;=1.3.0&quot;\nimport asyncio\nimport json\nimport os\n\n# Browser Use reads this once, when its tools load, so set it first.\nos.environ.setdefault(&quot;BROWSER_USE_ACTION_TIMEOUT_S&quot;, &quot;330&quot;)\n\nfrom browser_use import ActionResult, Agent, BrowserSession, ChatAnthropic, Tools\nfrom capskip import AsyncCapSkip, CapSkipError\n\nsolver = AsyncCapSkip(\n    apiKey=os.getenv(&quot;CAPSKIP_API_KEY&quot;, &quot;capskip&quot;),\n    host=os.getenv(&quot;CAPSKIP_HOST&quot;, &quot;127.0.0.1&quot;),\n    port=int(os.getenv(&quot;CAPSKIP_PORT&quot;, &quot;8080&quot;)),\n)\ntools = Tools()\n\nFIND = &quot;&quot;&quot;() =&gt; {\n  const w = document.querySelector(\n    '.g-recaptcha[data-sitekey], .cf-turnstile[data-sitekey]');\n  if (!w) return null;\n  return {\n    kind: w.classList.contains('cf-turnstile') ? 'turnstile' : 'recaptcha',\n    sitekey: w.dataset.sitekey,\n    invisible: w.dataset.size === 'invisible' || w.tagName !== 'DIV',\n    enterprise: !!document.querySelector(\n      'script[src*=&quot;recaptcha\/enterprise.js&quot;]'),\n    callback: w.dataset.callback || null,\n  };\n}&quot;&quot;&quot;\n\nFILL = &quot;&quot;&quot;(kind, token, callback) =&gt; {\n  const name = kind === 'turnstile' ? 'cf-turnstile-response'\n                                    : 'g-recaptcha-response';\n  const fields = document.querySelectorAll(`[name=&quot;${name}&quot;]`);\n  fields.forEach(f =&gt; { f.value = token; });\n  if (callback &amp;&amp; typeof window[callback] === 'function') {\n    window[callback](token);\n  }\n  return fields.length;\n}&quot;&quot;&quot;\n\n\n@tools.action(\n    &quot;Solve the reCAPTCHA v2 or Cloudflare Turnstile widget on the current &quot;\n    &quot;page. Call it after the other fields are filled, then submit the form &quot;\n    &quot;unless the page has already moved on.&quot;\n)\nasync def solve_captcha(browser_session: BrowserSession, page_url: str):\n    page = await browser_session.must_get_current_page()\n    raw = await page.evaluate(FIND)\n    widget = json.loads(raw) if raw else None\n    if not widget:\n        return ActionResult(error=&quot;No reCAPTCHA or Turnstile widget found.&quot;)\n    try:\n        if widget[&quot;kind&quot;] == &quot;turnstile&quot;:\n            result = await solver.turnstile(widget[&quot;sitekey&quot;], page_url)\n        else:\n            extra = {&quot;invisible&quot;: 1} if widget[&quot;invisible&quot;] else {}\n            result = await solver.recaptcha(\n                widget[&quot;sitekey&quot;], page_url,\n                enterprise=int(widget[&quot;enterprise&quot;]), **extra)\n    except CapSkipError as exc:\n        return ActionResult(error=f&quot;CapSkip did not solve it: {exc!r}&quot;)\n    filled = await page.evaluate(\n        FILL, widget[&quot;kind&quot;], result[&quot;code&quot;], widget[&quot;callback&quot;])\n    if filled == &quot;0&quot;:\n        return ActionResult(error=&quot;Solved, but no response field to fill.&quot;)\n    return ActionResult(\n        extracted_content=f&quot;Solved the {widget['kind']} CAPTCHA on {page_url} &quot;\n                          &quot;and filled in the token. Submit the form now, &quot;\n                          &quot;unless the page has already moved on.&quot;,\n    )\n\n\nEXTRA = (\n    &quot;This browser does not solve CAPTCHAs on its own, whatever the rules &quot;\n    &quot;above say. When a page shows a reCAPTCHA or Turnstile widget, fill in &quot;\n    &quot;the other fields, call solve_captcha, then submit the form unless the &quot;\n    &quot;page has already moved on. Never click the CAPTCHA checkbox or &quot;\n    &quot;challenge yourself.&quot;\n)\n\n\nasync def main():\n    agent = Agent(\n        task=&quot;Sign up at https:\/\/example.com\/signup with YOUR_EMAIL, &quot;\n             &quot;then report what the page says.&quot;,\n        # Adaptive thinking lets Browser Use leave the tool choice to Claude.\n        llm=ChatAnthropic(model=&quot;claude-opus-5-5&quot;, thinking={&quot;type&quot;: &quot;adaptive&quot;}),\n        tools=tools,\n        extend_system_message=EXTRA,\n        step_timeout=420,\n    )\n    history = await agent.run(max_steps=25)\n    print(history.final_result())\n\n\nasyncio.run(main())<\/pre>\n<\/div>\n<p>We ran this loop end to end against test pages for a checkbox widget, an invisible widget bound to a button, an Enterprise page and a Turnstile widget, with a scripted stand-in for the model so each step was predictable. The prompt arrives with the extra paragraph after the default rules, solve_captcha appears among the actions the model can choose, the token lands in the form, the data-callback function fires, the tool&#8217;s instruction reaches the model, and the submit is accepted. We also captured the request ChatAnthropic sends, to confirm that adaptive thinking leaves the tool choice to the model. With a real model the only difference is that the model decides when to call the tool, which is what the description and the extra paragraph are for. The raw endpoints behind both methods are documented in <a href=\"https:\/\/capskip.com\/api-docs\/\">the API reference<\/a>.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Common errors and what they mean<\/h2>\n<table>\n<thead>\n<tr>\n<th>What you see<\/th>\n<th>Cause<\/th>\n<th>Fix<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>The agent waits, scrolls or gives up on a page with a CAPTCHA, and never calls the tool<\/td>\n<td>The default system prompt says CAPTCHAs are solved automatically, and nothing corrected it<\/td>\n<td>Pass extend_system_message with the paragraph from Step 2<\/td>\n<\/tr>\n<tr>\n<td>Every step fails with a 400 saying the tool_choice type is not supported for the model<\/td>\n<td>ChatAnthropic was created without thinking, so Browser Use forced the tool choice<\/td>\n<td>Pass thinking={&quot;type&quot;: &quot;adaptive&quot;} to ChatAnthropic<\/td>\n<\/tr>\n<tr>\n<td>A step timed out after 180 seconds<\/td>\n<td>A slow solve plus the model call outlived step_timeout<\/td>\n<td>Pass step_timeout=420 to Agent, and raise the action cap as well<\/td>\n<\/tr>\n<tr>\n<td>Action solve_captcha timed out after 180s. The browser may be unresponsive (dead CDP WebSocket).<\/td>\n<td>With step_timeout raised, the solve outlived the per-action cap; the browser is fine<\/td>\n<td>Set BROWSER_USE_ACTION_TIMEOUT_S above 300 before Browser Use loads<\/td>\n<\/tr>\n<tr>\n<td>The variable is set, and the cap is still 180 seconds<\/td>\n<td>It was set after Agent or Tools had already been imported<\/td>\n<td>Move the assignment to the top of the entry script, above every import that pulls in browser_use<\/td>\n<\/tr>\n<tr>\n<td>The whole agent freezes while a CAPTCHA is solved<\/td>\n<td>The synchronous CapSkip client is blocking the event loop the browser connection runs on<\/td>\n<td>Use AsyncCapSkip and await its methods<\/td>\n<\/tr>\n<tr>\n<td>The tool reports that no widget was found<\/td>\n<td>The page renders the widget from script without data-sitekey, puts it in an iframe, or uses a different CAPTCHA<\/td>\n<td>Read the sitekey from the widget&#8217;s own request in DevTools, and extend FIND and FILL for that page; both run in the top-level document, so a form inside an iframe needs its own lookup<\/td>\n<\/tr>\n<tr>\n<td>Solved, but no response field to fill<\/td>\n<td>The Turnstile widget renames its field with data-response-field-name, or turns it off with data-response-field<\/td>\n<td>Read that attribute in FIND and write the token into the named field<\/td>\n<\/tr>\n<tr>\n<td>The token is filled in, and the site still rejects the submit<\/td>\n<td>The token expired before the submit, or the form waits for a callback it registered in script<\/td>\n<td>Have the agent solve right before submitting, and call the page&#8217;s own callback if data-callback is empty<\/td>\n<\/tr>\n<tr>\n<td>The page uses reCAPTCHA v3 on a button<\/td>\n<td>FIND treats it as v2<\/td>\n<td>Call solver.recaptcha with version v3 and the page&#8217;s action<\/td>\n<\/tr>\n<tr>\n<td>The tool&#8217;s error is a NetworkException<\/td>\n<td>CapSkip is not running, or the host and port are wrong for where the agent runs<\/td>\n<td>Start CapSkip, then check whether it should be in Local mode or Server mode<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">FAQ<\/h2>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">Does Browser Use solve CAPTCHAs by itself?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Only on its cloud browsers, where the solving happens in Browser Use&#8217;s own proxy and the library waits for it. The open-source library ships nothing that solves a CAPTCHA on a browser you run yourself, whether that is a local Chromium or your own Chrome reached through a CDP URL. That is the setup this guide is for.<\/p>\n<\/details>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">Can I give the agent CapSkip&#8217;s MCP server instead?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">You can. Browser Use can load the tools of an MCP server into its Tools instance, and CapSkip&#8217;s MCP server offers a solve tool for each supported type. Those tools return the token, though, so the model then has to write it into the page itself with a script of its own. That is two decisions for the model instead of one, and the second has to match each page&#8217;s markup. The custom tool keeps the reading and writing of the page in code. The MCP route suits agents you cannot add code to, and <a href=\"https:\/\/capskip.com\/mcp-captcha-solver\/\">the MCP CAPTCHA solver page<\/a> shows how to connect one.<\/p>\n<\/details>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">What about hCaptcha or a full-page Cloudflare challenge?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">CapSkip does not solve hCaptcha, which is why FIND matches only the reCAPTCHA and Turnstile classes and never an h-captcha widget. A full-page Cloudflare challenge is a different flow from the Turnstile widget: it needs the challenge&#8217;s cData and chlPageData values and the user agent returned with the token, as <a href=\"https:\/\/capskip.com\/cloudflare-turnstile-solver\/\">the Cloudflare Turnstile solver page<\/a> explains, so this tool does not cover it as written. Other CapSkip types, such as GeeTest or Capy Puzzle, can be added as further branches in the same tool.<\/p>\n<\/details>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">Can an agent running in the cloud reach my solver?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Yes. Put CapSkip in Server mode under connection settings so it listens on a network address instead of loopback, read that address from CAPSKIP_HOST where the agent runs, and pass it to AsyncCapSkip. A VPS, a container host, a CI runner and a scheduled job on a cloud platform all connect over the same HTTP API. Use a static public IP and a firewall rule when the route crosses the internet. The solver stays on hardware you own, so a long agent run that meets a CAPTCHA on every page costs nothing extra.<\/p>\n<\/details>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">The short version<\/h2>\n<p>Browser Use CAPTCHA handling on your own browser comes down to four changes, plus a fifth when the agent moves off the solver&#8217;s machine. Register a solve_captcha tool that reads the sitekey with page.evaluate, awaits AsyncCapSkip and writes the token back, calling the widget&#8217;s callback if it has one. Correct the default rule with extend_system_message so the agent calls the tool right before it submits. Pass adaptive thinking to ChatAnthropic so Claude accepts the request. Raise BROWSER_USE_ACTION_TIMEOUT_S before Browser Use loads and step_timeout on the Agent, both above the SDK&#8217;s 300 seconds. And switch CapSkip to Server mode when the agent runs anywhere but the solver&#8217;s machine.<\/p>\n<ul>\n<li>Every CAPTCHA type the Python package handles: <a href=\"https:\/\/capskip.com\/python-captcha-solver\/\">the Python CAPTCHA solver page<\/a>.<\/li>\n<li>How the checkbox and invisible widgets work: <a href=\"https:\/\/capskip.com\/recaptcha-v2-solver\/\">the reCAPTCHA v2 solver page<\/a>.<\/li>\n<\/ul>\n<p>An agent that browses for hours can meet a challenge on every other page, and a <a href=\"https:\/\/capskip.com\/\">captcha bypass<\/a> on your own Windows machine solves each one with no per-solve charge.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Browser Use \u544a\u8bc9\u5b83\u7684\u6a21\u578b\u9a8c\u8bc1\u7801\u4f1a\u88ab\u81ea\u52a8\u8bc6\u522b\uff0c\u800c\u8fd9\u53ea\u5728\u5b83\u7684\u4e91\u7aef\u6d4f\u89c8\u5668\u4e0a\u6210\u7acb\u3002\u672c\u6587\u7ed9\u51fa\u4e00\u4e2a\u81ea\u5b9a\u4e49\u5de5\u5177\uff0c\u7528 CapSkip \u5728\u4f60\u81ea\u5df1\u7684 Chromium \u4e0a\u8bc6\u522b\u9a8c\u8bc1\u7801\uff0c\u5e76\u8bf4\u660e\u9700\u8981\u8c03\u9ad8\u7684\u4e24\u4e2a 180 \u79d2\u65f6\u9650\u3002<\/p>","protected":false},"author":1,"featured_media":26174,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Browser Use CAPTCHA Solving With a Custom Tool | CapSkip","rank_math_description":"Browser Use captcha handling on your own browser: register a solve_captcha tool that calls CapSkip, fill the token and raise the 180 s limits. Code inside.","rank_math_focus_keyword":"browser use captcha","footnotes":""},"categories":[70],"tags":[],"class_list":["post-26175","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-captcha"],"_links":{"self":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/26175","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/comments?post=26175"}],"version-history":[{"count":3,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/26175\/revisions"}],"predecessor-version":[{"id":26180,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/26175\/revisions\/26180"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media\/26174"}],"wp:attachment":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media?parent=26175"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/categories?post=26175"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/tags?post=26175"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}