feat(profile): run an unattended scenario file in web profiling - #476
Conversation
`just web profile` only recorded a session that a human drove and stopped with Ctrl-C, so an agent could capture nothing beyond page load. `--scenario channels` waits for live coverage, opens the first five sidebar channels twice (cold, then warm), saves the app's existing client-metrics export as client-metrics.json and ends the capture. A step that does not finish within 60 seconds fails the run. The manifest records the scenario and BUZZ_RELAY_URL so staging and production runs can be told apart. The scenario only selects sidebar channels. Focus never enters the timeline, so the reading policy publishes no read markers. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Signed-off-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
The first cut built one channels workload into the harness, so every new
workload needed a commit and had to exist on each commit under
comparison.
`--scenario <file>` now takes any module, inside or outside the
repository, that default-exports `async (page, { signal }) => {}`. The
harness keeps only the generic parts: it loads the file before starting
anything, runs it against the Playwright page, saves the client-metrics
export as client-metrics.json and ends the capture. A scenario that
throws or does not finish within five minutes fails the run with the
capture still saved. The manifest records the resolved scenario file.
Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
Signed-off-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
wesbillman
left a comment
There was a problem hiding this comment.
One change needed: abort the scenario when its deadline expires so outstanding abort-aware work cannot keep an unattended run alive (details inline).
Star Lord automated source review via Wes’s account (wesbillman). Head e76a8faed0592f4ba5190ffdb67b79380bfad659; base 73064e34dfa5f14c472c3661589d05f509c17884. Source-only: no tests, scenarios, or app execution. The hosted snapshot shows required CI and DCO passing; that does not validate the missing timeout-cleanup case or native behavior.
…elay A scenario's signal aborted only on Ctrl-C, so abort-aware work left behind at the timeout, or when Vite exited, kept the process alive after the capture was saved. The signal now also aborts at the timeout, once the scenario settles, and whenever the capture ends. The manifest recorded the raw BUZZ_RELAY_URL process variable: it kept a value the dev server rejects, which may carry a credential, and missed a relay set in .env.local. It now records the validated origin of the development environment Vite loads, or null. A failed scenario now names the retained artifact directory and prints the scenario's stack. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Signed-off-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
wesbillman
left a comment
There was a problem hiding this comment.
No changes requested. The follow-up addresses the prior timeout finding: scenario-scoped cancellation now aborts outstanding work on timeout/finalization, and capture shutdown also aborts it when Vite exits; regression tests cover cancellation and process exit.
Star Lord automated source review via Wes’s account (wesbillman), head 0adccc90060ac973c5d92c85d200be05c1605aa3, base 73064e34dfa5f14c472c3661589d05f509c17884. Source-only follow-up: no tests, scenarios, or app execution; hosted CI was still running at the snapshot, and the author’s live-run evidence was not independently reproduced.
Web profiling forwards --mode to Vite, but the manifest always resolved the relay for development mode. With a mode-specific .env file the capture recorded a relay other than the one Vite used. The profiler now takes the mode itself (--mode or -m, once), resolves the relay for it, and launches Vite with the same mode. Spellings Vite would read as a mode without the profiler seeing one (a short-flag group such as -dm, or --m) are rejected. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Signed-off-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
wesbillman
left a comment
There was a problem hiding this comment.
No changes requested in this follow-up: the manifest and launched Vite now use the same normalized mode, with relay validation and cancellation cleanup preserved. I also inspected the updated public description and changed files; no attached media was present.
Star Lord automated source review via Wes’s account — head 13fc39293a014f8549ae8f30a5dbd9027985e774, base 73064e34dfa5f14c472c3661589d05f509c17884. Source-only: no tests or live profiling run; the current-head CI snapshot still has jobs running.
* origin/main: (82 commits) Test provider connections before model selection (#500) Bundle Goose ACP with Buzz (#497) Discover saved identities across joined communities with names, pictures and retry (#291) Clarify design-system documentation and unify component examples (#498) feat(composer): convert typed Markdown live and refuse control characters committed as text (#455) fix(messages): stop three timeline scroll races that flake CI (#456) Improve Agent defaults pickers and provider keys (#392) fix(threads): keep thread history painted after scroll corrections (#493) feat(plugins): expose the agent protection service (#421) perf(sidebar): re-render only the changed row on a channel-list publish (#480) feat(agents): copy protection defaults into new agents (#420) feat(agents): support native launch protection providers (#415) fix(composer): prevent WebKit overpainting mention selections (#490) fix(composer): prevent arrow keys from inserting control characters (#488) perf(channels): fall back to one exact roster read when confirming agent adds (#485) fix(media): pause video only on comment composer focus (#483) fix(channels): dismiss management modals with outside clicks (#479) perf: reuse message date formats and stable reaction shortcuts (#477) feat(profile): run an unattended scenario file in web profiling (#476) feat(channels): administer channel members and roles (#453) ... Signed-off-by: John Tennant <jtennant@block.xyz> # Conflicts: # src/app/shell/usePanelLauncher.ts # src/bundled/agents/AgentsPage.tsx # src/bundled/agents/InventoryIdentityCard.tsx # src/bundled/agents/InventoryView.tsx # src/bundled/agents/UnifiedInventory.tsx # src/bundled/agents/index.tsx
* origin/main: (82 commits) Test provider connections before model selection (#500) Bundle Goose ACP with Buzz (#497) Discover saved identities across joined communities with names, pictures and retry (#291) Clarify design-system documentation and unify component examples (#498) feat(composer): convert typed Markdown live and refuse control characters committed as text (#455) fix(messages): stop three timeline scroll races that flake CI (#456) Improve Agent defaults pickers and provider keys (#392) fix(threads): keep thread history painted after scroll corrections (#493) feat(plugins): expose the agent protection service (#421) perf(sidebar): re-render only the changed row on a channel-list publish (#480) feat(agents): copy protection defaults into new agents (#420) feat(agents): support native launch protection providers (#415) fix(composer): prevent WebKit overpainting mention selections (#490) fix(composer): prevent arrow keys from inserting control characters (#488) perf(channels): fall back to one exact roster read when confirming agent adds (#485) fix(media): pause video only on comment composer focus (#483) fix(channels): dismiss management modals with outside clicks (#479) perf: reuse message date formats and stable reaction shortcuts (#477) feat(profile): run an unattended scenario file in web profiling (#476) feat(channels): administer channel members and roles (#453) ... Signed-off-by: Sol <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz> # Conflicts: # src/app/shell/usePanelLauncher.ts # src/bundled/agents/AgentsPage.tsx # src/bundled/agents/InventoryIdentityCard.tsx # src/bundled/agents/InventoryView.tsx # src/bundled/agents/UnifiedInventory.tsx # src/bundled/agents/index.tsx
Problem
just web profilerecords only a session that a person drives and stops with Ctrl-C. An unattended run can capture page load and nothing else, and it never exits on its own.Change
just web profile --scenario <file>runs a scenario file against the page and ends the capture itself. No scenario lives in the repository.Scenario file: any JavaScript module, inside or outside the repository, that default-exports
async (page, { signal }) => {}. It gets the Playwrightpage.signalaborts on Ctrl-C, at the timeout, and when the capture ends for any reason (including Vite exiting), so abort-aware work a scenario leaves behind cannot keep the command alive. Because the file can live outside the tree, the same scenario runs unchanged against any commit that has this flag.Harness: loads the file before starting Vite or Chrome, runs it, saves the app's existing client metrics export as
client-metrics.json, stops the capture and exits 0.Failure: a scenario that throws, or does not finish within five minutes, exits 1. The capture is still finalized, no
client-metrics.jsonis written, and the command printsScenario failed; artifacts remain at <directory>.followed by the scenario's stack. A missing file or a module without a default function fails before anything starts.Ctrl-C during a scenario behaves as before: it exits 0 like any interrupted capture and writes no
client-metrics.json.Manifest: gains
scenario(the resolved file path) andrelay.relayis thehttps://origin of theBUZZ_RELAY_URLthat Vite resolves for its mode from the process environment or the.envfiles (process environment wins), validated by the app's ownrelayOrigin. A value the dev server would reject (credentials, path, query, fragment, wrong scheme, malformed) is recorded asnull, never verbatim. This applies to every web capture, with or without--scenario.coveragelistsclient-metricsfor scenario runs.Mode: the profiler takes the Vite mode itself (
--mode <mode>,--mode=,-m, once; defaultdevelopment), resolves the relay for it, and launches Vite with the same mode. Spellings Vite would read as a mode without the profiler seeing one (a short-flag group such as-dm, or--m) are rejected before the capture.The CLI now prints the stack of a wrapped failure's cause, which also covers the existing
Profiling stopped during startuperror.--scenariois rejected fordesktop. There is no new metrics code: the numbers are the__buzzClientMetricsexport that dev builds already produce. Thejustfileis unchanged becausejust web profilealready forwards its arguments.A scenario runs as the developer's real account, so keeping it read-only is the scenario author's job. The harness adds no writes of its own.
Evidence
node --test tests/integration/profile-dev.test.mjs: 47 pass, 0 fail at13fc3929. Twelve tests cover the change:--mode, missing value, duplicates, and the rejected-dm/--mspellings;.env.localonly, process environment over.env.local, a mode's own.env.<mode>over.env.local, and six rejected forms from either source;.env.localthrough Vite's realloadEnv;-m stagingrecords the.env.stagingrelay and launches Vite with--mode staging.Mutation checks, each restored afterwards:
runScenariodoes not abort its signal, or passes the scenario the parent signal: the timeout test fails;process.env: the relay and manifest tests fail;developmentregardless of mode, the mode is not passed on to Vite, or the-dm/--mrejection is removed: the mode tests fail.bin/lefthook run check-staged: pass.Live, macOS, headed Chrome, Vite dev server, staging relay. The scenario file lives outside the repository (listed below) and the same command ran three times in a row:
The
--networklog of run 1 has nopublish,read-state-publishorsignrequests.--scenario nope.mjsfailed before creating a profile directory (ate76a8fae).Real Vite 8.3.0 given the profiler's normalised arguments reports the same mode the profiler resolved, for no mode and each accepted spelling.
Live at
13fc3929, relay set only in.env.staging:--mode stagingran the channels scenario to exit 0 with"relay": "https://<staging>"; without--modethe manifest has"relay": null;-dm stagingwas refused before Vite started.Live at
0adccc90, same setup:Scenario did not finish within 300 seconds., and left no profiler or Vite process.Scenario failed; artifacts remain at …, then the stack pointing at the scenario's own line..env.local, none in the process environment: the channels scenario exited 0 (11 opens, p50 196 ms) and the manifest has"relay": "https://<staging>".BUZZ_RELAY_URL='wss://user:<canary>@<staging>/?token=<canary>': Vite refused to start, the manifest has"relay": null, and the canary appears in neither the profile directory nor the command output.channels.mjs used for the live runs (not part of this PR)
Limitations
relayis resolved from the repository's own Vite configuration. A capture started with a different--configis not modelled.node_modules.BUZZ_DEV_OPEN_RELAY=1is set or the scenario selects one.client-metrics.jsonneeds the dev-only__buzzClientMetrics, and the capture runs against the Vite dev server. Differences between runs are meaningful; absolute timings are not production numbers.Not covered
tests/integration/*.test.mjslocally at the first commit: 147 of 174 pass. The 27 failures are in the agent-runtime, launcher and worktree-icon tests (spawn …/bin/rustc EACCES,just installexit 126), files this change does not touch. Hosted CI passed on the first commit. The full suite was not rerun locally for0adccc90or13fc3929. One76a8fae,Browser journeys (webkit, 3/6)failed once intests/browser/todos.spec.mjs:9and passed on a rerun of that job with no change.To test
Save a scenario anywhere, for example
/tmp/wait.mjs:Chrome opens, the app loads its community, and the command exits 0 without input after printing the
.profiles/...-webdirectory. That directory containsclient-metrics.json, andmanifest.jsonhas"scenario": "/tmp/wait.mjs"and the relay as anhttps://origin.