Fix benchmark trace publication - #176
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 003080a. Configure here.
| } | ||
|
|
||
| export function redactString(value: string, maxLength = 20_000): string { | ||
| const TYPED_CALL = /\.(?:fill|type)\(([^)]*)\)/g; |
There was a problem hiding this comment.
bmsaadat
left a comment
There was a problem hiding this comment.
Review
Verdict: Request changes (two fixes; direction is right)
The trace shape is a real improvement: inputs on llm spans, phase timeline, browser lifetime, cached-token metrics. The PurelyMail preflight is the right fix for the setup-failure class, and the step-level exception classification matches how harbor 0.21.0 records step failures (multi_step.py stores them on the step and the trial completes normally). Verified locally at 2586a70: 281 tests, tsc --noEmit, prettier, and bash -n run.sh all pass. I also checked the new fields and retry names against the pins (harbor 0.21.0, harbor-hypeman 0.1.2, kernel/ClawBench c7feaa2) rather than the PR text.
Requested changes
-
A
)inside a typed value defeats both the redactor and the safety assert.TYPED_CALLcaptures with[^)]*, so the argument list is cut at the first), the selector gets redacted, and the value is published.assertSafeToPublishre-runs the same truncated scan, so it passes. Reproduced at head:page.fill('#password', 'Str0ng)Pass!') -> page.fill('[REDACTED]', 'Str0ng)Pass!') assert passes page.locator('#pw').fill('abc)def') -> unchanged assert passesMulti-line template literals,
pressSequentially, andkeyboard.insertTextare also uncovered. The benchmark's own password istoken_urlsafeso it cannot trigger this; it needs an agent-invented value, which is what the 38 sign-up tasks and 2 password-manager tasks produce. Suggest tokenizing the argument list to find the matching close paren, addingpressSequentially|insertText, letting the literal regex span newlines, and giving the assert a check that does not sharetypedCallValues, so fail-closed stays independent of the redactor. -
Row-level cost is gone, and the per-span replacement is uneven across agents.
metricRecorddroppedcost_usdand the token counts from the root row (main published harbor'sagent_result.cost_usd, which both converters set). The replacement readsstep.metrics.cost_usdon llm spans: harbor's Codex converter sets it per API call (litellm estimate), but the Claude Code converter never sets it per step (_build_metricswritescost_usd=None; onlyfinal_metrics.total_cost_usdandagent_result.cost_usdare set), so a Claude Code run publishes no cost anywhere.estimated_costis Braintrust's documented cost field. Suggest keeping row-level metrics under canonical names (prompt_tokens=n_input_tokens, which harbor documents as cache-inclusive;prompt_cached_tokens=n_cache_tokens;completion_tokens;tokens;estimated_cost=cost_usd) and emittingestimated_coston llm spans only when ATIF carries it. The synthetic fixture putscost_usdon the step, which is what hid the Claude Code gap.
Non-blocking
- Private-info harvesting scrubs ordinary words out of the whole trace.
privateInfoValuescollects every JSON string value of 4+ chars from a my-info read (172 unique values from the pinnedalex_green_personal_info.json) and substring-replaces them across every string in the trial. With the real values,{"name":"Email"} <button>Close Window</button> "Single sign-on" {"width":1208}becomes{"name":"[REDACTED]"} <button>Close [REDACTED]</button> "[REDACTED] sign-on" {"width":[REDACTED]}. The fake IDs and account numbers are worth scrubbing; the names and common words are not. Harvesting only from sensitive-shaped keys would keep the protection and the readability. --retry-include RuntimeErrordoes not retry step setup failures. harbor's retry loop returns as soon as the trial-levelexception_infois None, and a failedsetup.shis stored on the step (multi_step.py:_run_step_setup) while the trial completes normally, which is exactly the shape the new fixture models.AgentSetupTimeoutErroris raised at trial level and does retry. The README line should say agent setup only; the preflight is the real fix for the setup.sh class. Matching is by exact class name, so whatRuntimeErrornow catches is harbor-hypeman's bare raises, transient and deterministic alike; a named exception for the transient paths in harbor-hypeman would let us drop the bare include.- LLM-typed spans carry tool time. ATIF has one timestamp per step, so the turn interval includes the neighbouring tool execution, which dominates browser tasks, and Braintrust charts
type: "llm"as model latency. The metadata label is honest; naming the spanturnwould make it visible in the UI. llmInputpublishes only the previous agent step.prior.slice(previousAgent, previousAgent + 1)is a one-element slice, so user/system steps between two agent turns are dropped despite the README's "preceding context".prior.slice(previousAgent)gives the claimed behavior; the fixture has one agent step so the branch is untested.- Fail-closed publish with no locator.
assertSafeToPublishruns after all arms are built and throws a generic message with no trial, span, or field, so with the CI gate a red publish is a red multi-hour run debuggable only locally. Keep fail-closed, add the event id and key path to the error. Set-Cookieobject keys are no longer redacted.SENSITIVE_FIELDSholds"set-cookie"butnormalizedFieldrewrites-to_first, so it never matches (main caught^SET-COOKIE$). Small exposure since object keys only come from tool arguments and the string-level cookie rule still covers header text, but it shows the key vocabulary is now hand-copied into five lists that already disagree (the assert omitstoken, the collector knows five names, the URL regex lacks four). One exported list with a pattern builder would prevent the next drift.- Smaller: insert batches are count-only (100 events) while llm inputs now carry the previous step's observations, so a byte bound would be cheap insurance; llm/tool span IDs now key on
trajectoryIndex, so re-publishing an already-published job dir leaves the old spans orphaned (rows are still replaced); harvested secrets apply to ATIF spans only, not the root row'sinstruction/error; the preflight runs once per arm and a faileddeleteUserleaves acbpreflight…account behind;sources[arm].stats.nErroredTrialsstill comes from harbor's trial-level count, so it reads 0 for an arm thatbenchByArm.infraErrorssays had step failures.
Question
- The README previously kept task instructions off experiment rows; this publishes the redacted last user message as
input.instruction. ClawBench's instruction is the public prompt plus browser rules, the my-info file list, and extra_info names and descriptions, so it looks fine. Just confirming the policy change is deliberate.

summary
validation
bun test(281 tests)bunx tsc --noEmitbunx prettier --check ...bash -n benchmarks/harbor/clawbench/run.shgo run github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 .github/workflows/benchmark-clawbench.yml