Skip to content

fix(cursor): reject WebFetch requests in band without hosted execution - #520

Open
JS-banana wants to merge 1 commit into
openpi-dev:mainfrom
JS-banana:fix/cursor-webfetch-rejection
Open

fix(cursor): reject WebFetch requests in band without hosted execution#520
JS-banana wants to merge 1 commit into
openpi-dev:mainfrom
JS-banana:fix/cursor-webfetch-rejection

Conversation

@JS-banana

@JS-banana JS-banana commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Problem

Screenshot 2026-09-09 at 11-11-13

When using a Cursor-backed model with Pi tools enabled (e.g. cursor/cursor-grok-4.6-high), web fetch requests trigger a server-side interactionQuery (field 9: webFetchRequestQuery). Previously, the provider immediately threw an error upon receiving any interactionQuery, terminating the entire AgentService/Run with stopReason: "error". This left the model no opportunity to recover, fall back to advertised Pi tools, or explain the limitation to the user.

Closes #519
Related to #234, #484.

Value

Cursor-backed turns can gracefully handle hosted web fetch requests without aborting. The provider denies cloud-hosted web fetching in band, preserving Pi's exclusive tool ownership and permission boundaries while allowing the Run to continue to advertised Pi tools or clear textual explanations.

Approach

  • Rejection-only wire surface: In extensions/ai-providers/cursor/proto.ts, define AgentClientMessage.interactionResponse (field 6), response id (field 1), and WebFetchRequestResponse (field 9) with only the rejected (field 2, carrying reason: string) branch. No approved schema or hosted execution path is defined, ensuring strict fail-closed behavior.
  • In-band rejection & turn continuation: In extensions/ai-providers/cursor/provider.ts, handle webFetchRequestQuery by sending a correlated interactionResponse with distinct feedback depending on whether Pi tools are advertised or in chat-only mode, allowing the Run to continue.
  • Bounded recovery: Allow up to 3 WebFetch rejections per Run (MAX_WEB_FETCH_REJECTIONS = 3). On the fourth request, send the rejection and then settle explicitly with a terminal recovery-limit error, preventing infinite model loops.
  • Defense in depth: Other interactionQuery cases (such as askQuestionInteractionQuery, switchModeRequestQuery, exaSearchRequestQuery) continue to fail closed explicitly. No MCP tools are renamed or filtered, and existing native exec handling (fix(cursor): recover from native tool requests without native execution #484) is untouched.

Validation

  • Targeted provider tests:
    node --test --experimental-strip-types tests/extensions/ai-providers/cursor.test.ts passed: 34 passed, 0 failed.
    Coverage includes:
    • Wire verification asserting byte-for-byte encoding/decoding of interactionResponse and webFetchRequestResponse.rejected;
    • In-band rejection with text continuation across both chat-only and tool-enabled modes;
    • Full Agent-level roundtrip: WebFetch query rejected, followed by successful Pi read tool execution and result replay;
    • Bounded rejection limit: 4 rejections delivered, followed by explicit recovery-limit error;
    • Preserved fail-closed behavior for unsupported interaction queries.
  • Repository checks:
    bun run check passed: config contract, discipline ledger, Web build & typecheck, Biome format check, Biome lint, and tsc --noEmit.
  • Full test suite:
    bun run test passed: Node 1,469 passed / 1 skipped / 0 failed; Vitest 9 files, 130 passed.
  • Diff check:
    git diff --check passed.
  • Real account validation (evidence captured in isolated runner):
    Using Pi 0.85.1 and real account cursor/cursor-grok-4.6-high:
    1. --tools read: The model requested web fetch, received the rejection, and concluded with assistant stop explaining the inability to fetch, exiting 0 without terminal error.
    2. --tools read,bash: The model requested web fetch, received the rejection, successfully switched to the advertised Pi bash tool to run curl -fsSL --max-time 20 https://example.com/, received the HTML output, and concluded with assistant stop reporting the page title <title>Example Domain</title>, exiting 0.
      (Evidence boundary: While live validation confirmed successful Pi tool fallback under read,bash, this is an exploratory recovery opportunity and not a claim that all models or prompts are guaranteed to recover automatically.)

Impact

  • User-visible behavior: Cursor models requesting hosted web fetches no longer crash the turn with an unhandled exception.
  • Model-visible context/tools: Models receive clear protocol-level rejection reasons upon requesting hosted WebFetch. Advertised Pi tool schemas and permissions remain unchanged.
  • Runtime/lifecycle: Adds correlated interactionResponse handling and bounded rejection tracking within streamCursor.
  • Persisted config/data: None.
  • Compatibility/risk: Strict fail-closed stance is maintained (hosted fetching is never approved; other interaction queries still fail).

When a Cursor-backed model requests hosted WebFetch, the provider previously aborted the entire Run. This adds an in-band rejected response without granting hosted execution authority, lets the Run continue to Pi-owned tools or textual explanation, and bounds repeated requests to 3 rejections per Run.

Closes openpi-dev#519
Related to openpi-dev#234, openpi-dev#484

@tt-a1i tt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at exact head 6dd788c. Standards and Spec both pass with no P0-P3 findings. The wire surface is rejection-only, hosted WebFetch execution remains unavailable, Pi retains tool and permission ownership, unsupported interaction queries still fail closed, and recovery is bounded. Exact-head targeted tests and hosted CI are green.

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.

bug(cursor): Cursor WebFetch interaction query aborts a tool-enabled turn

2 participants