← Back to PRs

#23075: fix(browser): merge top-level ref/targetId into act request body

by Remixer33 open 2026-02-22 00:21 View on GitHub →
agents size: XS
## Summary - Fixes the browser tool `act` action failing with `"ref is required"` when the LLM passes `ref` as a top-level tool parameter instead of nesting it inside the `request` object - The `BrowserToolSchema` exposes `ref` at both the top level and inside `BrowserActSchema`, so LLMs (particularly Gemini via LiteLLM) commonly place it at the top level - The handler now spreads `params.request` and merges in `params.ref` and `params.targetId` when present, matching what the CLI commands (click, type, hover) already do ## Root cause `BrowserToolSchema` defines `ref` as an optional top-level field (line 102) and `BrowserActSchema` also defines `ref` inside the `request` object (line 52). When an LLM places `ref` at the top level, the `case "act"` handler forwarded only `params.request` to the `/act` endpoint — which didn't contain `ref`. The backend validation then rejected the request. This made **all** browser interactions (click, type, hover) from the agent fail consistently, even on trivial static pages like example.com. ## Test plan - [x] Verified fix works locally with OpenClaw agent — snapshot then click succeeds - [x] `pnpm check` passes (formatting, type checking, linting) - [x] `pnpm test` — 854 files pass, 6742 tests pass (7 pre-existing symlink failures on Windows unrelated to this change) - [x] CLI browser commands continue to work unchanged - [x] When `ref` is already inside `request`, the top-level value is not used (no override) ## AI disclosure This PR was AI-assisted (Claude Opus 4.6). The bug was diagnosed by tracing the code path from the agent tool handler through to the backend `/act` endpoint validation. The fix was tested on a live OpenClaw instance before submitting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR fixes the browser tool `act` action failing with `"ref is required"` when LLMs (e.g., Gemini via LiteLLM) place `ref` and `targetId` as top-level tool parameters instead of nesting them inside the `request` object. The fix spreads `params.request` into a mutable copy and merges in top-level `ref`/`targetId` when present (without overriding values already in `request`). - **Logic bug**: The spread-then-validate pattern (`{ ...(params.request as ...) }`) breaks the `"request required"` guard. `{ ...undefined }` produces `{}` in JavaScript, which is truthy and passes `typeof === "object"`, so the validation can never throw when `params.request` is missing. The validation should happen *before* spreading into a new object. - The merge logic itself (lines 790-795) is correct: it only fills in `ref`/`targetId` when the request object doesn't already contain them, preventing unintentional overrides. <h3>Confidence Score: 3/5</h3> - The core idea is sound but the implementation has a validation bypass bug that should be fixed before merging. - The PR correctly identifies a real issue (LLMs placing ref/targetId at the top level) and the merge logic is correct, but the refactoring of the spread operator breaks the existing "request required" validation — { ...undefined } returns {} which is truthy, so missing request objects will no longer be caught early. - `src/agents/tools/browser-tool.ts` — the spread-then-validate pattern on lines 781-783 needs to be reordered to validate first, then spread. <sub>Last reviewed commit: b2edbcf</sub> <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs