Skip to content

fix(tools): ship the browser recording JS helpers in the wheel - #4445

Open
onatozmenn wants to merge 1 commit into
OpenHands:mainfrom
onatozmenn:fix/ship-browser-use-js
Open

fix(tools): ship the browser recording JS helpers in the wheel#4445
onatozmenn wants to merge 1 commit into
OpenHands:mainfrom
onatozmenn:fix/ship-browser-use-js

Conversation

@onatozmenn

@onatozmenn onatozmenn commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

HUMAN:

I first checked whether the six JS files were the only missing runtime assets, or if something else was being left out of the wheel too. Turns out js/*.js was the whole gap; everything else is either already packaged or just contributor documentation.


AGENT:

Why

Fixes #4443.

browser_use/recording.py loads six helpers by name from _JS_DIR = Path(__file__).parent / "js" (recording.py:46, read at :84). openhands-tools/pyproject.toml declared package-data only for py.typed, **/*.j2 and the subagent prompts, so the js/ directory never reached the built wheel. agent-server.spec already asks for it with collect_data_files("openhands.tools.browser_use", includes=["js/*.js"]), but there was nothing installed to collect, and a frozen agent-server raised FileNotFoundError on browser_start_recording.

Nothing in a source checkout surfaces this. An editable install reads the files straight from the working tree, so the tests pass and the tool works right up until someone installs a wheel.

Summary

  • openhands-tools/pyproject.toml declares "openhands.tools.browser_use" = ["js/*.js"].
  • A tests/cross check walks every non-Python file under openhands/tools, skips the contributor docs, and asserts a package-data glob covers the rest.

Issue Number

Fixes #4443

How to Test

Clean wheel build, before and after. The build directory has to be removed between runs or the previous build's build/ and .egg-info are reused and the second measurement is meaningless:

cd openhands-tools
rm -rf build openhands_tools.egg-info
python -m pip wheel . --no-deps --no-build-isolation -w /tmp/w
python - <<'PY'
import zipfile, glob
w = sorted(glob.glob("/tmp/w/*.whl"))[-1]
print([n for n in zipfile.ZipFile(w).namelist() if "browser_use/js/" in n])
PY

On main that prints []. With this change it prints all six helpers:

before (clean build)   0 js entries
after  (clean build)   6 js entries
  flush-events.js            197 b
  rrweb-loader.js           2469 b
  start-recording-simple.js  502 b
  start-recording.js         725 b
  stop-recording.js          368 b
  wait-for-rrweb.js          591 b

The new test fails on main naming exactly those six files, and passes with the change:

uv run pytest tests/cross/test_package_data.py

tests/cross: 327 passed, 49 skipped. Two failures are pre-existing on this Windows host and not related to this change: test_generate_baseline_payloads_uses_uv_with_release_cutoff fails identically with the change stashed, and test_websocket_attach_wait_does_not_block_ready_endpoint is a 0.5s timing bound that passes on re-run.

ruff format --check and ruff check are clean on the new file.

Video/Screenshots

Console output above; this path has no UI surface.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • I checked whether anything else was missing rather than just the reported files. The only non-Python files under openhands/tools are the six .js, six .md and py.typed; of the markdown, terminal/README.md and AGENTS.md are contributor docs that nothing reads at runtime, and the subagent prompts already ship. So js/*.js is the whole gap.
  • The subagent prompts ship even though openhands/tools/preset/subagents/ has no __init__.py, which is why the test resolves a dotted package-data key to a directory rather than to an importable package. That matches what the wheel actually contains.
  • The test is a static check of the globs, not a build. It catches an undeclared asset, which is the failure mode here, but it cannot catch a build backend that ignores a correctly declared glob. Building a wheel per test run seemed too heavy for what it buys.
  • agent-server.spec needs no change, as the reporter noted.

recording.py loads six helpers from `Path(__file__).parent / "js"`, but
openhands-tools declared package-data only for py.typed, Jinja templates and
the subagent prompts. The JS never reached the built wheel, so
collect_data_files in agent-server.spec collected an empty set and a frozen
agent-server raised FileNotFoundError on browser_start_recording.

A clean wheel build shows the difference: zero js/ entries before, all six
after. An editable install reads the files straight from the working tree, so
nothing in a source checkout surfaces this.

The added test walks every non-Python file under openhands/tools that is not a
contributor doc and asserts a package-data glob covers it, so a new runtime
asset cannot be added without shipping it. It reproduces the same verdict the
wheel build gives, including that openhands.tools.preset.subagents ships fine
despite having no __init__.py.

Reported by @apps3000 in OpenHands#4443, with the PyInstaller root cause.

Signed-off-by: onatozmenn <onatozmen44@gmail.com>
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: f944b87fb448dfdce3a9a78313bc2c96a104c24f
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/fd705f44-f935-4778-8d00-32afcf29a562

This comment was posted by an AI agent (OpenHands).

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Summary

This PR fixes #4443 by declaring the six browser recording JS helpers as package data in openhands-tools/pyproject.toml. browser_use/recording.py loads these files by name from _JS_DIR = Path(__file__).parent / "js" (recording.py:46, read at :84), but the [tool.setuptools.package-data] table only covered py.typed, **/*.j2, and the subagent prompts, so the js/ directory never reached a built wheel, and a frozen agent-server raised FileNotFoundError on browser_start_recording. The one-line addition "openhands.tools.browser_use" = ["js/*.js"] correctly closes that gap.

I verified the fix end-to-end against the workspace:

  • With the fix, all six JS helpers (plus the four subagent .md files and py.typed) are reported as declared by the test logic.
  • Without the fix (simulating main), the test correctly identifies exactly the six JS files as undeclared.
  • The agent-server.spec already contains collect_data_files("openhands.tools.browser_use", includes=["js/*.js"]) (agent-server.spec:132), so once the wheel ships the files, the PyInstaller binary picks them up with no spec change, consistent with the PR description.

Correctness

The package-data key "openhands.tools.browser_use" resolves to the openhands/tools/browser_use/ directory, and the glob js/*.js matches all six helper files at browser_use/js/*.js. This is the correct setuptools package-data declaration.

Test Quality

The new tests/cross/test_package_data.py is a solid static regression guard. Rather than building a wheel per test run (which the PR notes would be too heavy), it walks every non-Python file under openhands/tools, skips known contributor docs (AGENTS.md, README.md), and asserts each remaining file is covered by a package-data glob. This would have caught the exact omission in #4443, and it also serves as a forward-looking guardrail: any future runtime asset added without a declaration will fail this test.

The dedicated test_browser_recording_js_helpers_are_declared is a belt-and-suspenders check that specifically asserts the browser_use/js/*.js files are covered, good for producing a focused failure message if this specific regression returns.

One minor observation (not blocking): _is_declared uses fnmatch to match patterns, but fnmatch and setuptools' pathlib.glob have slightly different semantics for **. For example, fnmatch("foo.j2", "**/*.j2") returns False while pathlib.Path.glob("**/*.j2") matches top-level files. This discrepancy is not currently exercised (there are no .j2 files under openhands/tools today, and all current patterns produce correct results), so it's purely a latent robustness note for if a top-level .j2 template is ever added.

Risk Assessment

Low risk. This is a one-line packaging declaration plus a regression test. The change is purely additive: it only causes the six JS files to be included in the built wheel; it removes nothing and changes no runtime behavior. An editable/source checkout already reads these files from the working tree, so the fix only affects wheel-based installs (which is exactly where the bug manifested). No security concerns: no untrusted input, no secrets, no new dependencies.

Verdict

No material issues. The fix is correct, minimal, and well-tested. Ship it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

agent-server 1.41.0 PyInstaller bundle omits browser_use/js (rrweb recording)

2 participants