maestro/docs/tools/runuserscript.md
oss-sync 9f8958c4a2
Some checks failed
CI / build-and-test (push) Has been cancelled
sync: update from private repo (ce93095)
2026-06-10 03:52:37 +00:00

4.4 KiB

RunUserScript

Executes a user-authored Playwright browser-macro from the caller's browser-macros/ folder.

Retired (2026-06): plain-Node scripts/ (the old kind: 'script') and templates/ were removed. Keep reusable procedures/boilerplate in Skills, and run ad-hoc code (Node, Python, …) with the Bash tool. Passing kind: 'script' now returns an error pointing at those replacements.

directory runtime signature use case
browser-macros/ Playwright — Chromium main({ context, params }) Web automation with a live browser session

Input

{
  name: string,                         // filename — '.js' is appended if absent
  params?: Record<string, unknown>,     // runtime values matching the macro's param spec
}

Param validation

Params are validated against the params: block in the macro's YAML frontmatter:

  • Extra params not listed in the spec → error containing "param"
  • Wrong type for a declared param → error containing "param"
  • Missing required param (no default) → error containing "param"
  • Params with defaults are filled in automatically when not supplied

On any param error the tool returns isError: true immediately — no subprocess is spawned.

Session integration

If the macro's frontmatter declares session_profile_id: <N>, the tool:

  1. Loads the profile from the DB (owner-gated — must belong to ctx.userId).
  2. Decrypts the user's envelope-encrypted DEK using the master key.
  3. Decrypts the AES-GCM storageState blob using the DEK.
  4. Passes the decrypted Playwright storageState object to the child process.

If any step fails the tool returns isError: true with a descriptive message.

Self-healing recorder

When a macro fails at runtime, the tool automatically enables the BrowseWeb recorder for the current task (if not already enabled). On task completion, recording-flush stages a candidate patch as browser-macros/{name}.next.js for diff review.

Output format

On success:

<result stringified>

[script logs]
<console.log lines from the child process>

The result is JSON-stringified if it is an object or array; String(result) otherwise. The [script logs] section is only appended when the macro produced logs.

On failure:

RunUserScript "{name}" failed: <error message>

The recorder is now enabled for this task; subsequent BrowseWeb actions will be captured.
On task complete, a candidate patch will be saved as browser-macros/{name}.next.js for review.

Error cases

Situation isError message contains
No authenticated user true "authenticated"
Macro file not found true "not found"
Retired kind: 'script' passed true "retired"
Frontmatter parse error true "frontmatter"
Param type / missing error true "param"
Session profile not found / not owned true "not found or does not belong"
Profile not active true "not active"
DEK / blob decryption failure true "decrypt"
Macro timeout (60 s) true "timeout"
Macro exits non-zero true "exited code"

Notes

  • The tool is a META_TOOL — it is available in every movement without listing it in allowed_tools.
  • Use ListUserAssets first to discover available macros and their param specs.
  • On macro failure, use BrowseWeb as a manual fallback.
  • To run Python or other ad-hoc code, use the Bash tool (pip packages pre-baked).

Security and trust model

RunUserScript is disabled by default. To enable it, add to config.yaml:

tools:
  user_scripts_enabled: true

Only enable for trusted users. Macros run in a restricted child process:

  • Env is scrubbed — only PATH, HOME, TMPDIR/TMP, LANG, NODE_ENV, and PLAYWRIGHT_BROWSERS_PATH are forwarded. API keys, database passwords, and other secrets in the orchestrator's environment are not visible to the macro.
  • CWD is set to the system tmpdir, not the orchestrator workspace.
  • Stdout is capped at 1 MB and stderr at 200 KB; exceeding either limit kills the child.
  • On timeout, the entire process group (including Playwright's Chromium) is killed.

Browser-macros cannot use Node's --permission model — Chromium launch, native bindings, and outbound HTTPS all need unrestricted child_process/addons/network. They run with full Node.js capability (env-scrubbed only) and rely on container-level isolation. Treat them as trusted code.