## Summary
Fixes#4065 - OpenAI reasoning tokens were being double-counted in
`_get_usage()`, inflating reported token counts by ~2x for all reasoning
models (o1, o3, o4-mini, etc.).
## Problem
The `_get_usage()` method was incorrectly adding `reasoning_tokens` to
`completion_tokens`:
```python
completion_tokens = response.usage.completion_tokens
if completion_token_details is not None:
reasoning_tokens = completion_token_details.reasoning_tokens
if reasoning_tokens is not None:
completion_tokens += reasoning_tokens # ← Double counting!
```
## Root Cause
According to OpenAI's API documentation, `completion_tokens` already
**includes** `reasoning_tokens` as a subset. This is explicitly stated
in the `CompletionTokensDetails` field description:
> reasoning tokens are "counted in the total completion tokens for
purposes of billing, output, and context window limits"
This differs from Google Gemini where `thinking_tokens` are reported
separately and need to be added.
## Solution
Removed the `reasoning_tokens` addition and used `completion_tokens`
directly, with a clarifying comment explaining the difference from
Gemini.
## Impact
- ✅ Fixes inflated cost calculations for users of o1, o1-pro, o3,
o3-mini, o3-pro, o4-mini
- ✅ Fixes inflated usage logging and telemetry
- ✅ Aligns with other OpenAI-compatible providers in the codebase
(OpenRouter, Vercel, Cerebras, Groq)
- ✅ No behavioral impact (token counts aren't used for budget limits or
context window management)
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes double-counting of OpenAI reasoning_tokens in _get_usage(), which
inflated completion token and cost numbers for reasoning models. Aligns
token reporting with OpenAI docs and fixes#4065.
- **Bug Fixes**
- Use response.usage.completion_tokens directly; remove added
reasoning_tokens.
- Corrects usage and cost logs for o1, o1-pro, o3, o3-mini, o3-pro,
o4-mini.
<sup>Written for commit 993a4c1f5b.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
OpenAI's completion_tokens already includes reasoning_tokens as a subset,
so adding them again was incorrectly inflating token counts by ~2x for
all reasoning models (o1, o3, o4-mini, etc.).
This aligns with other OpenAI-compatible providers (OpenRouter, Vercel,
Cerebras, Groq) which use completion_tokens directly.
Note: This is different from Google Gemini where thinking_tokens ARE
reported separately and need to be added.
Fixes#4065
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Fixed JSON corruption in structured outputs by changing how the simple
judge records its note. When a schema is expected, the note is stored in
metadata instead of appended to extracted_content.
- **Bug Fixes**
- When output_model_schema is set, write the simple judge note to
result.metadata['simple_judge'] (initializing metadata if needed).
- Preserve previous behavior (append to extracted_content) when no
structured output is expected.
<sup>Written for commit 07817c2c5b.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
## Summary
Fixes#3970
The LLM was typing literal placeholder names (e.g., `user_name`) instead
of wrapping them in `<secret>` tags (e.g.,
`<secret>user_name</secret>`), causing the password/sensitive data
replacement mechanism to fail.
## Changes
Updated
[_get_sensitive_data_description()](cci:1://file:///Users/ashishkumarcheruku/Desktop/BrowserUse/browser_use/agent/message_manager/service.py:372:1-401:11)
in
[browser_use/agent/message_manager/service.py](cci:7://file:///Users/ashishkumarcheruku/Desktop/BrowserUse/browser_use/agent/message_manager/service.py:0:0-0:0):
- Added clear "SENSITIVE DATA" header to draw attention
- Changed placeholder list format from Python list representation to
bullet points
- Added explicit "MUST" instruction emphasizing the required format
- Added a concrete example using the first actual placeholder name
- Added explanation that the system will replace tags with actual values
## Before
Here are placeholders for sensitive data: ['user_name', 'pass_word'] To
use them, write the placeholder name
## After
SENSITIVE DATA - Use these placeholders for secure input:
pass_word
user_name
IMPORTANT: When entering sensitive values, you MUST wrap the placeholder
name in tags. Example: To enter the value for "pass_word", use:
pass_word The system will automatically replace these tags with the
actual secret values.
## Testing
- All 10 existing sensitive_data tests pass
(`tests/ci/security/test_sensitive_data.py`)
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Clarifies sensitive-data guidance so placeholder names must be wrapped
in <secret> tags, preventing secret replacement failures and addressing
#3970.
- **Bug Fixes**
- Added a "SENSITIVE DATA" header and bullet list of placeholders.
- Added a MUST instruction with an example to wrap names in <secret>
tags.
- Explained that tags are auto-replaced with real secrets.
- Limited to _get_sensitive_data_description; synced with main, no
functional changes; all sensitive_data tests pass.
<sup>Written for commit f3ed2acb11.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
### Summary
This PR improves the robustness of sensitive data handling by allowing
the agent to recognize and replace placeholders even when the LLM
outputs them as literal strings without `<secret>` tags.
### Problem
In some cases (observed with certain LLMs), the model omits `<secret>`
tags and instead outputs the literal sensitive key name (e.g.
`user_name`, `pass_word`).
Previously, the library only scanned for tagged content, causing these
placeholder names to be typed into form fields instead of the actual
credentials.
### Solution
- Updated `Registry._replace_sensitive_data` in
`browser_use/tools/registry/service.py` to detect **exact string
matches** against registered sensitive data keys.
- Added support for **2FA / TOTP generation** for these literal matches.
- Preserved full backward compatibility with the existing tag-based
replacement logic.
### Impact
This prevents failed automation and accidental exposure of placeholder
names when LLMs "forget" to include `<secret>` tags, making credential
handling more reliable across different models.
### Related issue
Closes#3970
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Replaces exact literal secret names when the LLM omits <secret> tags,
including generating TOTP for bu_2fa_code. Stops placeholder strings
from being typed into fields and fixes#3970.
<sup>Written for commit f469617aa7.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
When the agent runs multiple actions per step, steps where all actions
fail weren't being counted as failures, so the failure limit could never
be reached. Now, any step ending in an error counts as a failure. May
help with #3615.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes failure counting for multi-action steps so the agent stops after
hitting the failure limit. Any step whose last action errors now
increments consecutive_failures, preventing the counter from being
skipped on multi-action error steps.
- **Bug Fixes**
- Count a step as a failure when the last result has an error,
regardless of action count.
- Prevents runaway loops where the failure limit was never reached.
<sup>Written for commit e1e170a5fd.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Redirect all logs to stderr to keep MCP stdio clean and avoid protocol
breakage. Set env vars early and adjust imports to stop early logs.
Fixes#2748.
- **Bug Fixes**
- Route all logs (app, CDP, MCP server) to stderr; MCP server runs at
WARNING.
- Print CLI addon warning to stderr.
- Set logging env vars before imports and reorder mcp/server imports to
prevent early logs.
<sup>Written for commit 53c1b0dfb9.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
Fixes: #3897
**The Problem**
When using browser-use with certain custom LLM proxies (configured via
ChatOpenAI
), the agent would crash unexpectedly during the execution loop. The
error traceback pointed to
browser_use/llm/openai/chat.py
with TypeError: 'NoneType' object is not subscriptable.
This happened because some proxies return a JSON response where the
choices field is either missing or explicitly set to None (likely due to
internal errors or non-standard formatting). The existing code assumed
response.choices always contained a valid list and attempted to access
response.choices[0] directly, causing the crash when the list was None.
**The Fix**
This PR modifies
browser_use/llm/openai/chat.py
to add robust validation for the LLM response structure.
**Identified the Failure Point:** Located the exact lines in
ainvoke
where response.choices[0] was accessed for both standard string
completions and structured output.
Added Guard Clauses: Implemented an explicit check: if not
response.choices:.
Improved Error Handling: Instead of letting the application crash with a
confusing Python TypeError, the code now raises a
**ModelProviderErro**r
with a specific message: "Model response missing 'choices' field. This
can happen with incompatible proxies."
This change allows the agent to handle these malformed proxy responses
gracefully, providing users with a clear error message that points to
the proxy configuration rather than a bug in the library code.
**Verification/ testing**
**Reproduction**: Created a reproduction script that mocked AsyncOpenAI
to return a ChatCompletion object with choices=None, successfully
replicating the TypeError.
**Validation**: Ran the same reproduction script with the fix applied;
confirmed that
ModelProviderError
was raised with the correct message.
**Regression Testing**: Verified that standard OpenAI usage remains
unaffected.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fix crash when some OpenAI-compatible proxies return chat completions
without choices. We now validate choices in ainvoke and raise a clear
ModelProviderError (with base_url hint) instead of a TypeError;
structured-output calls no longer depend on schema injection.
- **Bug Fixes**
- Validate response.choices in both text and structured paths; if
missing/empty, raise ModelProviderError (502) with a base_url hint and
preserve status/message.
- Use the validated first choice for content and finish_reason; keep the
structured output parse check.
- Structured-output request runs even when add_schema_to_system_prompt
is false; removed a temporary debug regression test and reverted the
prior CI timeout change.
<sup>Written for commit 668f51ead9.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
Fix indentation so structured-output request executes even when add_schema_to_system_prompt is false; add regression tests for choices=null in both structured and non-structured paths.
Avoid indexing response.choices[0] when proxies return choices=null/empty; raise a clear ModelProviderError (with base_url hint) and use the validated first choice for content/finish_reason
Fixes#3970
The LLM was typing literal placeholder names (e.g., 'user_name') instead
of wrapping them in <secret> tags (e.g., '<secret>user_name</secret>').
Changes to _get_sensitive_data_description():
- Added clear 'SENSITIVE DATA' header to draw attention
- Changed placeholder list format from Python list to bullet points
- Added explicit 'MUST' instruction emphasizing the required format
- Added concrete example using the first actual placeholder name
- Added explanation that the system will replace tags with actual values
This makes it much clearer to the LLM that it needs to wrap placeholder
names in <secret> tags for the automatic replacement mechanism to work.
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Bumps package version from 0.11.8 to 0.11.9 in pyproject.toml to prepare
the next patch release.
<sup>Written for commit d795dbe0f8.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
- When a user's output_model_schema has a field called success, the LLM
sees success at two nesting levels in the done action and gets confused.
It flattens them and drops data entirely
- bu-1-0 fails every time; bu-2-0 handles it fine
- Fix hides success from the schema sent to the LLM while keeping the
field on the model
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Hide StructuredOutputAction.success from the LLM-exposed JSON schema to
avoid collisions when user output models also define success. This
prevents the LLM from flattening fields and dropping data in done
responses.
- **Bug Fixes**
- Strip 'success' from StructuredOutputAction JSON schema via
json_schema_extra so the LLM only sees the user data schema.
- Keep success on the server model; done continues to serialize only
params.data (no breaking change).
<sup>Written for commit 04da1fd30c.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Adds automatic message compaction to keep the agent’s prompt small when
history grows large. This reduces token usage and prevents runaway
context size.
- **New Features**
- Configurable MessageCompactionSettings (step cadence, char/token
thresholds, keep_last_items, summary_max_chars, optional
include_read_state, optional compaction_llm).
- Summarizes long histories into compacted_memory and injects it at the
top of the prompt; keeps the first item and the last N items.
- Compaction runs during context prep, triggered on step intervals with
a char/token threshold floor.
- Sensitive data is filtered before compaction; token cost tracking
includes the compaction LLM.
- New state fields track compaction metadata; added prepare_step_state
and skip_state_update to avoid redundant updates.
<sup>Written for commit d1c9a38c00.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Softened the action-loop and page-stagnation nudges to be
awareness-focused, keeping detection thresholds unchanged and removing
directive language.
- **Refactors**
- Reworded loop nudges at 5/8/12 repeats to use “Heads up” and encourage
checking progress.
- Made page-stagnation message gentler and suggest trying a different
element or approach.
- Updated tests to match new phrasing and confirm no “done” or “cannot
be completed” directives.
<sup>Written for commit 3c57cdc756.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Increased the default max_steps from 100 to 500 in agent run and
run_sync to allow longer executions by default.
This lets agents complete more complex tasks without needing manual
configuration.
<sup>Written for commit 995cb0176b.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
The build context on line 27 was set to ../../.. but the script cd's
into docker/ at the start, so only .. is needed to reach the repo root.
Running it as-is would point docker build to the wrong directory and
fail.
Fixes#4011
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixed the Docker build context in docker/build-base-images.sh to use ..
instead of ../../.. because the script runs from docker/. This points
builds at the repo root and avoids failures; fixes#4011.
<sup>Written for commit 1d9ae7e8ca.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
Include in agent context that new tab was opened
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Notify the agent when a click opens a new browser tab so it knows to
switch to it. Addresses #3657 where the agent stayed on the old tab
after actions that opened a new page.
- **Bug Fixes**
- Detect new tabs by comparing tab IDs before and after clicks in
_click_by_coordinate and _click_by_index.
- Append a note to the action memory with the new tab’s short ID,
prompting the agent to switch.
<sup>Written for commit 99be7b1b48.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
#3063
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Add HarRecordingWatchdog to capture HTTPS network traffic via CDP and
write a HAR 1.2 file on browser shutdown. Auto-attaches when
browser_profile.record_har_path is set and supports configurable content
storage and capture modes.
- **New Features**
- Records HTTPS requests/responses (bodies and request postData) and
saves to browser_profile.record_har_path on stop.
- Supports record_har_content: embed (text or base64), attach (sidecar
dir: <har_stem>_har_parts), or omit.
- Supports record_har_mode: full or minimal (main doc + same-origin
subresources).
- Adds browser/version metadata, page refs, and DOMContentLoaded/load
timings.
- **Migration**
- Set browser_profile.record_har_path to enable.
- Optional: configure record_har_content (embed|attach|omit) and
record_har_mode (full|minimal).
<sup>Written for commit 9d81f64b2e.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Introduce HarRecordingWatchdog to capture HTTPS traffic via CDP and
write a HAR 1.2 file on shutdown, configurable for content storage and
capture scope, and auto-attached to sessions.
>
> - **Watchdogs**:
> - **HarRecordingWatchdog (new)**:
> - Captures HTTPS requests/responses via CDP (`Network`, `Page`) and
writes HAR 1.2 on `BrowserStopEvent`.
> - Configurable: `record_har_content` (embed/attach/omit) with sidecar
dir for attach; `record_har_mode` (full/minimal).
> - Records response bodies, headers, sizes, compression, timings;
includes pages with titles and DOMContentLoaded/load metrics; embeds
browser metadata.
> - **Session Integration**:
> - Import, initialize, and attach `HarRecordingWatchdog` in
`BrowserSession.attach_all_watchdogs()` (auto-enabled when
`record_har_path` is set).
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b53a593d33. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Raised loop detection thresholds and expanded the window to reduce false
positives and make nudges less aggressive. Also updated the simple judge
to include today's date so recent timestamps aren’t flagged as
fabricated.
- **Refactors**
- Increased default loop_detection_window from 10 to 20; repetition
thresholds now trigger at 5/8/12, and page stagnation at 5 consecutive
identical pages.
- Improved action hashing: renamed click_element/input_text to
click/input for hashing, scroll hashes include element index, and
wait/done/go_back actions are excluded from loop tracking.
- **Bug Fixes**
- Fewer false-positive loop warnings, especially around waits, terminal
actions, and repeated scrolls.
- Critical loop message no longer instructs calling “done”; it suggests
trying a different approach or reporting progress.
- Judge prompt includes today’s date and clarifies that near-current
timestamps are not fabricated, reducing false failures.
<sup>Written for commit 332694bf1a.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->