Add record start/stop CLI command for session video capture#4710
Conversation
Closes #4533. - `RecordingWatchdog` gains public `start_recording(path, size?, framerate?)`, `stop_recording() -> Path`, and `is_recording`; the existing `BrowserConnectedEvent`/`BrowserStopEvent` path is refactored to use them, so profile-driven recording behavior is unchanged. - `browser-use record start <path>` / `record stop` / `record status` subcommands wired through argparse, daemon dispatch, and the browser command handler. `record stop` prints the saved file path so it can be captured programmatically, matching the issue's requested UX. Works with `--session NAME` via the existing named-daemon infrastructure. - The CLI's `CLIBrowserSession` intentionally skips watchdogs; the handler lazily instantiates `RecordingWatchdog` on first `record start` so CLI recording doesn't pay the watchdog-setup cost for non-recording sessions. - Output format is `.mp4` (libx264) since that's what the existing `VideoRecorderService` encodes; optional dependency gate is unchanged (`pip install "browser-use[video]"`). - New `tests/ci/test_action_record.py` exercises the full stack against a real headless browser + `pytest-httpserver`, verifying decodable MP4 output, double-start rejection, stop-without-start no-op, that the existing `profile.record_video_dir` flow still works, and the argparse / dispatch wiring.
Agent Task Evaluation Results: 2/2 (100%)View detailed results
Check the evaluate-tasks job for detailed task execution logs. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1d933258c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
2 issues found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="browser_use/skill_cli/commands/browser.py">
<violation number="1" location="browser_use/skill_cli/commands/browser.py:782">
P1: The lazily created `RecordingWatchdog` is never attached to the session event bus, so its `AgentFocusChangedEvent`/`BrowserStopEvent` handlers do not run. Recording can miss tab switches and may not finalize cleanly on browser stop.</violation>
</file>
<file name="browser_use/browser/watchdogs/recording_watchdog.py">
<violation number="1" location="browser_use/browser/watchdogs/recording_watchdog.py:41">
P1: The refactored `on_BrowserConnectedEvent` now calls `start_recording()` which raises `RuntimeError` on failure (missing deps, viewport detection failure). Previously, both conditions were handled with early returns/warnings, allowing sessions with `record_video_dir` to degrade gracefully. Wrap this call in `try/except RuntimeError` and log a warning to preserve the prior behavior.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
- `on_BrowserConnectedEvent` now catches `RuntimeError` from `start_recording()` so sessions with `record_video_dir` configured but missing `[video]` extras (or a viewport that can't be sized) keep starting — prior graceful-degradation behavior is restored. - Lazy `RecordingWatchdog` in the CLI handler now calls `attach_to_session()`, so `AgentFocusChangedEvent` / `BrowserStopEvent` handlers are wired correctly if the session dispatches them. - Daemon shutdown finalizes any in-progress recording before tearing the browser down, preventing truncated MP4s on `close`, idle timeout, or signal-driven exit. - Added regression test that monkeypatches `start_recording` to raise and asserts `on_BrowserConnectedEvent` swallows it without breaking startup.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 132756dabb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="browser_use/skill_cli/daemon.py">
<violation number="1" location="browser_use/skill_cli/daemon.py:457">
P2: `asyncio.wait_for` actively cancels the wrapped coroutine on timeout — meaning the ffmpeg encoder close inside `stop_recording()` will be interrupted, potentially leaving a truncated/corrupt MP4. For longer recordings or slow storage, 5 seconds may not be enough for finalization. Consider using `asyncio.shield()` around `stop_recording()` or increasing the timeout significantly (e.g., 30s) to avoid data loss during graceful shutdown.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
`asyncio.wait_for(stop_recording(), timeout=5.0)` could expire while the ffmpeg encoder was still flushing, leading the daemon's subsequent `os._exit(0)` to kill the executor thread mid-write and leave the exact truncated MP4 this hook was meant to prevent. `stop_recording()` already offloads the blocking close to an executor, so awaiting it directly is safe — and if it genuinely hangs, a stuck daemon is a clearer failure signal than silent video corruption. Verified end-to-end: start recording → `open` → `close` (no explicit `record stop`) now produces a decodable MP4 with the captured frames.
Closes #4533.
Summary
Adds
browser-use record start <path>/record stop/record statusto capture the current session as an MP4 via CDP screencasting — all the underlying machinery (Page.startScreencast,VideoRecorderService) already existed in the repo; this just exposes it on the CLI.RecordingWatchdoggains a publicstart_recording(path, size?, framerate?)/stop_recording() -> Path/is_recordingAPI. The existingBrowserConnectedEvent/BrowserStopEventhandler is refactored to use it, so profile-driven recording (record_video_dir=...) is unchanged.recordsubcommand wired through argparse (skill_cli/main.py), the daemon dispatch allowlist, andskill_cli/commands/browser.py. Works with--session NAMEvia the existing named-daemon infrastructure.record stopprints the saved file path so it can be captured programmatically (as requested in the issue).CLIBrowserSessionintentionally skips watchdogs; the handler lazily attachesRecordingWatchdogon firstrecord startso non-recording sessions pay no cost..mp4(libx264) — matches the existing encoder. Gated behind the existingbrowser-use[video]optional extra; the CLI returns a helpful error if deps are missing.Example
browser-use --session demo record start /tmp/demo.mp4 browser-use --session demo open https://example.com browser-use --session demo click 3 browser-use --session demo record stop # /tmp/demo.mp4Test plan
uv run pytest -vxs tests/ci/test_action_record.py— 6 new tests, all pass (~23s). Covers: full start/stop cycle against a real headless browser (produces a decodable MP4), double-start rejection, stop-without-start returns None, profile-driven flow unchanged, argparse parsing, dispatch registration.uv run pyrighton changed files — clean.uv run ruff check/ruff format— clean.record start→open→record stopproduced a valid ~11 KB MP4.Summary by cubic
Adds
record start/stop/statusto thebrowser-useCLI to capture the current session as an.mp4via CDP screencasting, with simple start/stop APIs onRecordingWatchdogand reliable shutdown that finalizes recordings.New Features
browser-use record start <path>,stop, andstatus;startsupports--framerate,stopprints the saved path, andstatusreturns path, framerate, and size.--session NAME; lazily attachesRecordingWatchdogso non-recording sessions have no overhead..mp4(libx264) via the existing encoder; gated behindbrowser-use[video]with a clear error if missing.Bug Fixes
on_BrowserConnectedEventdegrades gracefully when recording cannot start (e.g., missingbrowser-use[video]or undetectable viewport) so sessions still launch withrecord_video_dirset.stop_recording()(no timeout) and finalizes any in-progress recording, preventing truncated MP4s.Written for commit 44f7ead. Summary will update on new commits.