feat: return actionable next steps with every error and add two workflow prompts - #68
Conversation
…low prompts The public error message is fixed per code and says only what went wrong. The remediation existed solely in docs-site/reference/errors.md, which a client choosing its next tool call never reads. Add a nextStep string to the public error object, keyed only by error code and built entirely from compile-time constants, so no failure can turn it into a leak channel. The exhaustive Record means a new error code fails to compile until it has one (ADR 0022). Union-typed arguments — locator, capture, condition, action, wait — carried no description. A client that flattens oneOf/$ref during schema ingestion left the caller an untyped object with nothing in the published surface stating the shape. Each union now carries a literal example. Add compare_page_states and form_validation prompts for the two recurring workflows the existing pair did not cover. Document every enforced bound in one place for the first time, and rewrite the error reference to quote what actually crosses the wire. AUDIT.md records the full audit, including the findings left as proposals: the support-request default (ADR 0023), the discovery-cost budget (ADR 0024), and element-action failure classification (ADR 0025).
Code review of this branch found four issues in the new nextStep field and its documentation. INTERNAL_ERROR pointed the client agent at the GitHub issue tracker and told it to ask the user. Bug reporting reaches an agent through the MCP instructions, which an operator drops with BROWSERMESH_AGENT_GUIDELINES=false (ADR 0021); repeating it on the error channel put the solicitation back past that opt-out, and an unattended run has nobody to ask. The next step now says only what the caller can act on. compare_page_states bounded its two labels at 200 characters while telling the agent to pass them to browser_session_create verbatim, where names cap at 128 and a longer one is rejected with LIMIT_EXCEEDED. Bounded at the limit that actually applies. errors.md claimed to quote the shipped strings and did not for INTERNAL_ERROR; README quoted the pre-change tools/list measurement, which the argument descriptions added here take from 87 KB to roughly 94 KB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Reviewed the diff against correctness, security, the AGENTS.md/SPEC contracts, and test coverage. The change is mostly additive and low-risk: a constant lookup table, five Contracts — clean. I spot-checked the new Two findings, both on the wire guidance itself rather than the mechanism, left as inline comments:
One documentation gap outside the diff hunks, so no inline: Minor, no action needed: the PR description says the new test "asserts a distinct next step for all twenty codes". It asserts each code has a non-empty step differing from its own message — exhaustiveness is covered by the 🤖 Generated with Claude Code |
Two next steps overstated what BrowserMesh knows about a failed call. `SerialQueue.run` re-checks the abort signal only after `await task()`, because an in-flight browser action cannot be aborted. A `browser_click` cancelled mid-flight can already have landed, so "Nothing was retained; reissue it" told an agent to repeat a destructive action the runtime had no reason to believe was discarded — contradicting the documented recovery rule on the channel the agent actually reads. `asBrowserMeshError` maps any unexpected throw to `INTERNAL_ERROR`, including one raised after the action completed, so "the operation left nothing behind" was the same claim. Both now qualify the retry and ask the caller to confirm the page state. `INVALID_ARGUMENT` is raised from around thirty sites, but its next step named only the locator/ref case. On `browser_observe` — the case the docs single out, where `limit: 150` passes the schema and the runtime rejects it against `maxPageSize` — that pointed the agent at two arguments the call never sent. It now names where the remaining bounds live instead. ADR 0022, the errors reference table, and its recovery section follow the wire strings. A test pins the retry qualification for both codes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ReviewThe core of this is sound. I checked the factual claims the new strings and docs make against the source, and the substantive ones hold:
Three findings, all posted inline, none blocking:
I could not run 🤖 Generated with Claude Code |
…turn `LIMIT_EXCEEDED` named `maxChars`, `maxBytes`, `maxRefs` and `limit` and then offered `browser_runtime_info` as the way to read them back. It returns none of the four: `runtimeInfo()` gives `ResourceLimits` — session labels, screenshot, visible text, persistence — plus the default timeout and the session and page counts. The snapshot bounds are fixed in the build and `browser_observe`'s `limit` is checked against `observability.maxPageSize`, which the tool does not echo. `INVALID_ARGUMENT` inherited the same claim, and there it landed on the case the docs single out: `limit: 150` passes the schema, the runtime rejects it, and `maxPageSize` is precisely what the caller cannot read back. Both next steps now say what the tool does report and that these bounds are not in it, so following them does not cost a call that learns nothing. The error-code test asserted each `nextStep` differed from its own message, which a table collapsed to one generic string would pass. It now also asserts the twenty are distinct from each other. `docs/IMPLEMENTATION_STATUS.md` reads present tense and still quoted the pre-ADR-0022 discovery figure and a two-prompt list. Both follow the current surface; ADR 0020's dated record is left as it stands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Review Reviewed the whole diff. The change is sound in its core: Correctness
Test coverage
Verified and fine
|
`SESSION_NOT_READY` told the caller the session was still being created and to retry. `readySession` raises it for any status that is not `ready`, `closing`, `closed`, or `failed`-after-disconnect — which includes a session whose creation failed without a Chromium disconnect. That entry keeps `status: "failed"`, survives `pruneTerminalSessions`, and is returned by `listSessions` and by `browsermesh://sessions`, so an agent can pick the id back up and retry forever on a session that will never become ready. The next step now reads the status first and names what each one means. Nothing asserted the five union argument descriptions reach `tools/list`. The byte budget is an upper bound that losing 6,516 characters would pass, and the `$ref` round trip compares compacted against uncompacted, so it stays green if both sides lose them. A new test pins a non-empty description on `locator`, `capture`, `condition`, `action`, and `wait` after reference resolution; deleting one `.describe()` fails it. `browser-tests (22)` failed on `goBack` losing the Chromium page target after a click-driven navigation, in a test about role-name matching that only needed to get back to the fixture. It navigates instead; back, forward, and reload keep their own test. Test counts in `CLAUDE.md` and `docs/IMPLEMENTATION_STATUS.md` were stale before this branch — 156/72/231 against a measured 179/78/260 on master. Both now read 179/83/265, which is what this branch runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Finding 1.22 said `LIMIT_EXCEEDED`'s next step points callers at `browser_runtime_info` for bounds it does not return. It no longer does — but naming the gap on the wire is a disclosure, not the fix the finding asks for, and the finding stands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Reviewed for correctness, leaks, the addressing/isolation contracts, and test coverage. The change is sound — two notes, both posted inline, neither blocking. What I verified
The strings are accurate, which for this feature is the whole risk. A
Declining to solicit bug reports from Union descriptions cover the whole surface. Walking every Contracts hold.
Notes
Test coverage for the behaviour introduced is good: all twenty codes, the leak channel, the two rollback caveats, the five described unions, both prompt renderings, the optional-argument branch, and URL rejection. I could not execute |
`browser_click` followed by `browser_back` is an ordinary sequence, and both operations run back to back on the same session queue. `PlaywrightBrowserEngine.click` awaits only `locator.click()`, so `goBack` can read the navigation history while the clicked navigation is still becoming the active document — Chromium answers `Page.getNavigationHistory` with "Not attached to an active page", and the caller gets `NAVIGATION_FAILED` for a page that was merely busy. It is what failed `browser-tests (22)` on the previous push. `back` and `forward` now wait for the document the page is already on to reach `load` first. That costs nothing on a settled page and is bounded by the same operation deadline, since `goBack` already waits for `load` on the document it lands on. Routing the role-name test around the sequence left it exercised nowhere; a dedicated test now drives click → back five times over. Both new prompts also read as if a session were addressable on its own. Every operation they name is page-specific, so each now carries the `parallel_roles` clause: address the sessionId and the pageId the creation returned, because there is no current or active page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Reviewed for correctness, leaks, the addressing/isolation contracts, and coverage of the new behaviour. What holds up
I checked every tool and argument name the strings mention against the registered surface — Prompts stay inside the boundary. Both are static templates over validated arguments; no runtime state, no LLM call, no notion of an agent. Both spell out the addressing rule. Union descriptions. Reasonable response to schema flattening, and the new test is honest about why it exists: the byte budget is an upper bound and the Two things
Coverage note The new One scope note: the |
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
What
Every application error now carries a
nextSteptelling the caller what to do about it, two workflow prompts joindiagnose_page, and the union-typed tool arguments describe their shape in prose.Actionable errors (ADR 0022)
applicationErrorResultreplaces every raw message with a fixed per-code string. Correct for the boundary, but eighteen of twenty codes then said only what broke — the remediation lived solely indocs-site/reference/errors.md, which a client choosing its next tool call never reads.A constant
nextStepper code now ships on the wire. Every value is a compile-time constant naming BrowserMesh tools and arguments; nothing is interpolated from an error, a page, a locator, or configuration, so the field cannot become a leak channel however the failure arose. An integration test asserts a distinct next step for all twenty codes and proves a hostile locator cannot reach it.INTERNAL_ERRORdeliberately says nothing about where to report. Bug reporting reaches an agent through the MCPinstructions, which an operator drops withBROWSERMESH_AGENT_GUIDELINES=false(ADR 0021); repeating the solicitation on the error channel would put it back past that opt-out, and an unattended run has nobody to ask.Union arguments described in prose
A client that flattens
oneOf/$refduring schema ingestion leaves the caller an untyped object, and nothing else in the published surface stated the shape.locator,capture,condition,action, andwaitnow carry their form in a description as well as in the union. That costs bytes —tools/listgoes from roughly 87 KB to 94 KB — which is the argument for ADR 0024's budget rather than a reason to leave the unions unusable.Two prompts
compare_page_states— load one URL in two isolated sessions and report only what differs. The two sessions are the point: one session would carry the first state into the second reading.form_validation— drive a form to its validation errors, in the page and in the console. It says explicitly never to submit real credentials.Documentation
docs-site/reference/limits.mdis new: every bound BrowserMesh enforces in one table, including the onesbrowser_runtime_infodoes not report.errors.mdnow quotes the strings actually sent. README gains a "when to use BrowserMesh, and when not to" section that names the workflows other servers serve better, and the first-run Chromium download that can trip a short client connect timeout.AUDIT.mdrecords the measurements behind all of this. Three findings it raises are proposed but not applied, because each changes a published contract: ADR 0023 (support-request opt-in), ADR 0024 (a discovery-cost budget), ADR 0025 (element-action failure classification — a locator that matches nothing currently reportsOPERATION_TIMEOUT, notELEMENT_NOT_FOUND).Verification
npm run verifygreen: 231 tests under the coverage thresholds, plus the build. Thetools/listbyte budget and the$refround-trip equivalence both still pass with the added descriptions.🤖 Generated with Claude Code