A1: propagate taskId through pilo server and core#389
Merged
Conversation
7 tasks
lmorchard
approved these changes
Apr 21, 2026
5 tasks
MrTravisB
added a commit
that referenced
this pull request
Apr 27, 2026
**Replaces #390** (auto-closed when its base branch was deleted by the A1 squash-merge). ## Summary Second PR in Stack A. Stacks on the merged A1 (#389). Adds structured classification fields to the error response so dashboards and programmatic retry logic can reason about failures without parsing `message` strings. Additive only - no existing field is removed. ### What's added - `error.class` - error constructor name (e.g. `"TypeError"`, `"SyntaxError"`) - `error.reason` - coarse-grained enum, safe for metric labels: `INVALID_REQUEST`, `PROVIDER_UNAUTHORIZED`, `NAVIGATION_TIMEOUT`, `BROWSER_DISCONNECTED`, `MAX_ITERATIONS`, `MAX_ERRORS`, `TIMEOUT`, `INTERNAL_ERROR` - `error.recoverable` - boolean derived from `error instanceof RecoverableError` - `error.phase` - which pipeline phase produced the error: `"setup"` or `"execution"` ### What stays (unchanged for consumers) - `error.message` - human-readable string, still present - `error.timestamp` - ISO timestamp, still present - `error.code` - fine-grained semantic code, still present - `error.taskId` - from A1, still present ### The data-sensitivity invariant `error.message` must never contain raw `error.message` of a thrown value (agent errors can quote task text, URL selectors derived from page content, or extracted values). The new code enforces this two ways: 1. **Validation / setup errors** use hardcoded string literals at the callsite ("Task is required", "AI provider is not configured.", etc.). Server-controlled by construction. 2. **Task execution errors** go through `errorResponseFromError`, which sets `message` from a fixed `REASON_HINTS` map keyed by the `reason` enum. No code path reads `error.message` of the thrown value into the response. A canary test throws `new Error("DO NOT LOG THIS SENTINEL")` and asserts the sentinel never appears in the serialized response. ### Minor security improvements (bundled) Dropped two WS error paths that previously echoed client-provided strings back in the message: - `UNKNOWN_EVENT` no longer quotes `msg.event` - `UNKNOWN_REQUEST_ID` no longer quotes the requestId - `INVALID_SEARCH_PROVIDER` no longer echoes the user-provided value ### Changes - `packages/server/src/taskRunner.ts` - new `ErrorReason` / `ErrorPhase` types, extended `ErrorResponse`, new `createErrorResponse` (object-param signature) and `errorResponseFromError` helpers, `classifyError` mapper, `REASON_HINTS` map. `errorToString` removed (no callers left). - `packages/server/src/routes/pilo.ts` - validation errors use new shape via `validateTaskRequest`; setup-error path uses `createErrorResponse` with explicit `reason: INVALID_REQUEST`; task-execution path uses `errorResponseFromError`. - `packages/server/src/routes/piloWs.ts` - all `createErrorResponse` callsites migrated; task-execution error uses `errorResponseFromError`; dropped client-provided values from error messages. - Tests: `taskRunner.test.ts` / `pilo.test.ts` / `piloWs.test.ts` updated to assert new fields alongside existing `message`/`timestamp`/`code`. Added canary tests that assert no sentinel string ever appears in the serialized response regardless of what the thrown value's `.message` is. ## Test plan - [x] `pnpm --filter pilo-server run test` (78 passing) - [x] `pnpm --filter pilo-core run test` (658 passing) - [x] `pnpm --filter pilo-cli run test` (221 passing) - [x] `pnpm run typecheck` green - [x] `pnpm run format:check` green
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First PR in Stack A of the Pilo reliability plan. Adds a server-generated
taskId(UUID v4) that threads through every task submitted viaPOST /pilo/run(SSE) or the WebSocket/pilo/runendpoint.The
taskId:x-pilo-task-idHTTP response header on every response(success, validation error, setup error) so callers like tabs-api can log
the taskId with the request in their access log without parsing the SSE body
startevent and a new WebSockettask:acceptedeventoptional
error.taskIdfieldrunTaskintoWebAgentoptions and is set as thepilo.task.idOTel span attribute on the root task spanPurely observational. No behavior change. Additive only: the new
error.taskIdfield is optional, the newtask:acceptedWS event andx-pilo-task-idheader are new, so existing clients are unaffected. Unblocksdownstream PRs that need per-task correlation in logs, metrics, and traces.
Changes
packages/core/src/webAgent.ts- optionaltaskIdonWebAgentOptions,set as
pilo.task.idspan attributepackages/server/src/taskRunner.ts- optionaltaskIdonTaskRunnerOptionsandErrorResponse.error;createErrorResponse(message, code, taskId?)signaturepackages/server/src/routes/pilo.ts- generate taskId at request entry,set
x-pilo-task-idresponse header, include instartevent and allerror responses, pass to
runTaskpackages/server/src/routes/piloWs.ts- generate taskId ontask:details,emit
task:acceptedevent, include in all error responses, pass torunTasktaskRunner.test.ts, 7 inpilo.test.ts, 4 inpiloWs.test.tsTest plan
pnpm --filter pilo-server run test(70 passing: 56 existing + 14 new)pnpm --filter pilo-core run test(658 passing)pnpm --filter pilo-cli run test(221 passing)pnpm run typecheckgreen across all packagespnpm run format:checkgreencurl -i -X POST /pilo/run -d '{}'returns 400 with anx-pilo-task-idresponse header anderror.taskIdin the body, bothmatching the same UUID v4
task:detailsreturnstask:acceptedevent withtaskIdbefore any agent events
Notes
mainerrorToStringwith a sanitized structured error shape(class + reason enum, never
error.message)gitleaksis not installed on my machine - I couldn't run the recommendedsecret scan, but the diff contains only UUID propagation and test values
like
"task-abc-123", no secrets