Skip to content

fix: Resolve pre-flight task failures synchronously#1080

Open
jirispilka wants to merge 1 commit into
claude/deduplicate-sync-task-paths-duv3zqfrom
fix/preflight-task-sync-resolve
Open

fix: Resolve pre-flight task failures synchronously#1080
jirispilka wants to merge 1 commit into
claude/deduplicate-sync-task-paths-duv3zqfrom
fix/preflight-task-sync-resolve

Conversation

@jirispilka

Copy link
Copy Markdown
Collaborator

What we're solving

A task-augmented tools/call that fails pre-flight (missing payment signature → paymentRequiredResult, standby rejection → standbyRejection) created a task, scheduled executeToolAndUpdateTask via setImmediate, and had it re-check the same values just to store the already-known error asynchronously. The client saw a fake working phase, and the task path carried dead re-check code that #1063's dispatch extraction would have to drag along. Closes #684 (step 2 of #658). Stacked on #1073 (base: claude/deduplicate-sync-task-paths-duv3zq).

How

In the handler's task branch, right after createTask: if a pre-flight value is set (standby priority first, matching the sync path), store it as the terminal completed result, emit a single completed status notification (no working transition), and return { task: { ...task, status: 'completed' } }. Telemetry rides the handler's existing finally, firing once with the same properties as the non-task path. The dead re-check block and its two carrier params are deleted from executeToolAndUpdateTask; the runtime 402/permission-approval handling there is untouched (pinned by the #1061 suite, which passes with zero assertion edits).

New tests: payment pre-flight (Skyfire-like provider fixture) and standby (mocked conflict check) both resolve synchronously; stored result deep-equals the non-task response including x402 structuredContent; standby-over-payment priority; exactly one completed / zero working notifications; tool call never invoked; telemetry shape per class; expiry edge (task evicted between create and store) still puts completed on the wire.

Alternatives considered

  • Return the failure with no task at all (the issue's original proposal) — not possible: SDK 1.29.0 validates task-augmented responses against CreateTaskResultSchema; a bare result surfaces as McpError(InvalidParams) instead of the x402 payload.
  • Throw McpError pre-flight — moves the payment payload out of structuredContent; breaks payment clients.
  • Shared pre-flight helper across sync/task paths — that unification is refactor: Extract dispatchToolCall with exhaustive TOOL_TYPE switch #1063; this PR stays a behavior fix.

⚠️ Flag for apify-mcp-server-internal contract suite

Wire-visible change: task-augmented pre-flight failures now return a CreateTaskResult that is already completed — previously the task transitioned through working and completed asynchronously. Notification timing changes accordingly (single completed, no working). RedisTaskStore needs no new transition (working → terminal via storeTaskResult is the existing completion path), but the contract suite may want a matching test.

Run evidence

  • type-check, lint, test:unit (980 passed / 2 skipped, deterministic across repeated runs), format, check:agents all green.
  • Red→green: new tests failed against the old async behavior before the fix; the expiry-edge fix reproduced expected 'working' to be 'completed' when reverted.
  • Review: staff-review PASS after one fix round (expired-task fallback could leak working onto the wire — fixed); final reviewers code-review and thermonuclear both PASS (0 findings); mcpc live-probe smoke PASS (async task lifecycle, pre-dispatch gate, error shapes intact).
  • Fulfillment: 13/13 success criteria MET.
  • Human follow-up: run pnpm run test:integration — the standby task-mode test (tests/integration/suite.ts:3129) is expected green and doubles as the SDK-client check for already-completed tasks.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LUZNJQKNCn7ntZCbYsFer5


Generated by Claude Code

A task-augmented tool call that fails pre-flight (missing payment
signature, standby rejection) created a task, then executeToolAndUpdateTask
re-checked the known outcome and stored it asynchronously — a zombie
working phase for a result decided before execution. The task branch now
stores the failure as the terminal result right after createTask and
returns a CreateTaskResult already completed, with a single completed
status notification. The dead pre-flight re-checks and their two carrier
params are removed from executeToolAndUpdateTask.

Returning the failure without any task (the fix #684 originally proposed)
is not possible: SDK 1.29.0 validates task-augmented responses against
CreateTaskResultSchema, so a bare result would surface as
McpError(InvalidParams) instead of the x402 payload. Throwing McpError
instead was rejected — it would move the payment payload out of
structuredContent and break payment clients.

Closes #684

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUZNJQKNCn7ntZCbYsFer5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants