Skip to content

[Bug]: Cursor client-tool continuation can return tool-call-like assistant text to Pi #2305

Closed

Description

@knttkd

Client or integration

Other — Pi coding agent (pi CLI) using OpenCodex as an OpenAI Chat Completions custom provider.

Area

Provider adapter

Summary

When Pi uses opencodex/cursor/grok-4.6, a Cursor-display MCP alias can be returned to Pi as the literal tool-call name instead of the request-declared Pi tool name.

For a Pi request that declares only grep, Cursor/Grok can return:

mcp_opencodex-responses_grep

Pi has no tool with that name. The turn stops after displaying the tool call and never executes grep or receives a tool result.

The expected client-facing name is the original request-declared name:

grep

This is not a request to permit undeclared tools. The long Cursor display alias should be normalized to the declared Responses/Pi tool name before OpenCodex emits the client-facing tool call.

Reproduction

  1. Run OpenCodex on loopback and configure Pi with the generated opencodex provider using api: "openai-completions".

  2. Start Pi with the Cursor-routed Grok model and allow only grep:

    pi --model opencodex/cursor/grok-4.6:high --tools grep --no-session
  3. Ask the model to search the current directory with grep. A normal call can succeed and is displayed by Pi as:

    grep OpenCodex
    ✓ 1 lines
    
  4. In the same Pi session, ask it to use the Cursor-side long display alias for the only available grep tool (or wait for Cursor to choose that display form).

  5. Pi receives the following instead of its declared tool name:

    [TOOL_CALL]mcp_opencodex-responses_grep[ARGS]{"pattern":"OpenCodex"}
    
  6. No normal grep execution/result follows. The turn is effectively blocked because Pi cannot dispatch a tool named mcp_opencodex-responses_grep.

The test was intentionally limited to one declared tool, so no unrelated tool catalog or MCP server was involved in the failure.

Version

OpenCodex: 2.28.0
Pi: 0.84.2

Operating system

macOS 26.5 (arm64)

Provider and model

provider: cursor
model: grok-4.6
OpenCodex selector: cursor/grok-4.6
reasoning effort: high
adapter: cursor

Logs or error output

Pi transcript from the failing turn:

Thinking...

長い表示別名の grep を1回だけ呼び出して、現在の作業ディレクトリを検索します。
[TOOL_CALL]mcp_opencodex-responses_grep[ARGS]{"pattern": "OpenCodex"}

The adjacent control turn in the same Pi session completed with the ordinary declared tool name:

grep OpenCodex
✓ 1 lines

OpenCodex recorded the requests as successful Cursor-adapter requests (provider: cursor, model: grok-4.6, adapter: cursor, HTTP status 200). The failure occurs on the tool-name translation returned to Pi, not at the upstream HTTP request boundary.

Redacted configuration

Pi provider shape:

{
  "providers": {
    "opencodex": {
      "baseUrl": "http://127.0.0.1:10100/v1",
      "api": "openai-completions",
      "apiKey": "opencodex-loopback"
    }
  }
}

Relevant existing behavior

The Cursor adapter already has display-name normalization for the mcp_opencodex-responses_ prefix. The observed Pi result indicates that at least one Cursor return path emits the display alias without applying that normalization to generic client tools such as grep.

The existing normalization currently appears intended to fold the display name back to the advertised wire name:

mcp_opencodex-responses_grep -> grep

Please apply the same invariant to every Cursor client-tool return path before emitting a Responses/Chat Completions tool call to the downstream client, and add a regression test with a non-shell generic tool such as grep.

Related issues

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    bugSomething isn't working
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    toolstool_calls, MCP, web-search / sidecar tools
    on Aug 21, 2026
  2. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Thanks for the concrete reproduction. I checked the exact Cursor MCP event shape against both v2.28.0 and current dev@c0cbe494e.

    The ordinary structured path already normalizes generic tools, not only the shell bridge. With a request catalog containing only grep, a Cursor mcpToolCall whose provider is opencodex-responses and whose tool name is mcp_opencodex-responses_grep maps to this downstream event sequence:

    tool_call_start name=grep
    tool_call_delta {"pattern":"OpenCodex"}
    tool_call_end
    

    I added and ran the exact grep regression locally; it passes without a source change. The relevant normalization code is unchanged between v2.28.0 and current dev. That means the transcript line [TOOL_CALL]mcp_opencodex-responses_grep... is not yet enough to identify the broken boundary: it may be a different Cursor protobuf event shape, or literal assistant text that Pi renders but cannot dispatch.

    Please attach one fresh reproduction with:

    • the Cursor provider-debug frames around the call, especially the toolCallStarted, partialToolCall, and toolCallCompleted cases;
    • Pi's raw structured event/JSON for that turn, showing whether it received a Chat Completions tool_calls[].function.name entry or plain assistant content;
    • the matching OpenCodex request-details row and /healthz version.

    Please keep the declared catalog limited to grep. If the raw event is a structured call that still carries the long alias, I can patch the specific missing return path. If it is literal model text, changing the tool-call mapper would not affect it and we need to fix the Cursor prompt/projection boundary instead. I am marking this needs-info rather than closing it because the user-visible failure is credible, but the currently claimed mapper path does not reproduce.

  3. added
    needs-infoWaiting on reporter for a concrete spec or reproduction
    on Aug 21, 2026
  4. lidge-jun commented on Aug 21, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 58 / 80

    지금 dev HEAD c0cbe494e에서 리포터가 가리킨 매퍼는 이미 접두를 접음. src/adapters/cursor/tool-definitions.ts:333-336 CURSOR_MCP_DISPLAY_PREFIX = mcp_opencodex-responses_. normalizeCursorWireName이 그 prefix를 slice. src/adapters/cursor/protobuf-events.ts:278-281 mcpWireNameFromArgs가 toolName||name에 그걸 탐. recordToolCall :1100-1116이 advertised catalog 이름으로 openToolCalls.name을 고정. commitToolCall이 그 이름으로 tool_call_start를 냄. 셸 브릿지만의 특수 케이스가 아님. generic grep도 같은 함수. Ingwannu 재현이랑 같음. tests/cursor-protobuf-events.test.ts:993-1010은 mcp_opencodex-responses_exec_command → exec_command만 잠금. mcp_opencodex-responses_grep 회귀는 현재 dev에 없음. 로컬에만 넣었다 함.

    Pi 경로. api: "openai-completions". Chat inbound → Responses 브릿지 → Cursor 어댑터 → src/chat/outbound.ts:262-275가 function.name을 tool_call_start/function_call item.name에서 복사. 매퍼가 grep을 내면 Pi도 grep을 받음. 반대로 긴 알리아스가 structured로 새면 src/bridge.ts:1041-1046 declaredToolNames 가드가 502 undeclared client tool. 리포터 HTTP 200. 그 가드랑 안 맞음. 긴 이름이 structured tool_calls[].function.name으로 나갔으면 200이 아님. 그래서 트랜스크립트 [TOOL_CALL]mcp_opencodex-responses_grep[ARGS]{"pattern":"OpenCodex"}만으로는 경계를 못 가름. Pi가 컨텐트 텍스트를 그렇게 그리는지, Chat tool_calls를 그렇게 그리는지 원본 JSON이 없음.

    재현 4단계가 모델을 긴 표시 알리아스로 부르라고 시킴. 일본어 씽킹도 「長い表示別名の grep」. Grok이 알리아스를 글자로 적고 Pi가 그걸 TOOL_CALL로 보여준 후보가 큼. 그 경우 매퍼 패치는 무효. 프롬프트/프로젝션 경계임. tool-definitions.ts:19-20/643 노트가 모델에게 mcp_opencodex-responses_*를 같은 툴이라고 말함. 호출은 접어서 나가게 되어 있음. 글자 나레이션까지 막진 않음. 다른 후보: mcpArgsFromToolCall :271-276이 providerIdentifier === opencodex-responses만 MCP로 봄. provider가 다르면 native-exec. grep는 Cursor native Grep가 아님. NEIGHBOR_AGENT_TOOL_NAMES Grep/grep 힌트(tool-definitions.ts:21-24)는 카탈로그에 없을 때 쓰지 말라는 문구. 매퍼 구멍으로 단정 금지.

    리포터 버전 2.28.0. 현재 dev는 v2.29.0 태그 + Bun 1.4.0 + Ox Alpha x-preview-f-free + deepseek-v4-flash-vision-exp. 접두 정규화 코드는 그 사이 안 바뀌었다 함. 같은 빌드에서 한 장이 성공(grep OpenCodex ✓ 1 lines) 한 장이 실패. 매퍼 on/off가 아님. 이벤트 셰이프/텍스트 경로 쪽이 맞음. #399는 shell_command/exec_command 브릿지. 이 이슈랑 합쳐서 닫지 말 것. 중복 아님. needs-info 유지. 구현 PR 지금 열지 말 것. 라벨 바꾸지 말 것. 이미 bug/needs-info/provider/tools.

    types.ts/config.ts 스플릿 안 씹힘. 스키마 이슈 아님. 리베이스하지 말고 닫으라는 케이스 아님. #2188 L1–L9 사이드카 이미 dev. x_search 넣지 말 것. Grok OAuth Chat 기본(#2255)/GUI 옵트인 Responses(#2266)/#2283이랑 다른 레인임. 프리뷰 배포 아님. #2292/#2293 윈도우 피커랑 안 겹침. 비전공자 유지. Pi 턴이 막히는 유저 보이는 실패라 58. 코드 패치 우선은 아님. 증거 먼저.

    해결방안: needs-info로 열어 둬라. 다음 재현에 (1) ocx debug provider on의 toolCallStarted/partialToolCall/toolCallCompleted (2) Pi가 받은 Chat Completions choices[0].delta.tool_calls[].function.name 원본 JSON인지 플레인 assistant content인지 (3) request-details + /healthz 버전. 카탈로그는 grep만. structured가 긴 이름을 들고 있으면 그때 그 리턴 패치를 짬. 텍스트면 매퍼 건드리지 말 것. tests/cursor-protobuf-events.test.ts에 clientToolNames: ["grep"] + mcp_opencodex-responses_grep → tool_call_start name=grep 회귀는 넣어도 됨. 그건 잠금이지 픽스가 아님. #399랑 머지하지 말 것. 스플릿이 Cursor 어댑터를 옮기면 리베이스하지 말고 닫고 다시 짜라. 지금은 그 정도 아님.

    이 댓글은 grok-bot이 작성했습니다

  5. knttkd commented on Aug 21, 2026

    @knttkd
    Author

    Thank you for building and maintaining OpenCodex. It has been genuinely useful in my daily workflow, and the Cursor/Grok integration is especially valuable. I also appreciate the time you have taken to investigate this report.

    Please feel free to treat this as low priority. I am sharing the additional evidence mainly to correct and narrow my original report, rather than to request an urgent fix.

    I reproduced the same class of behavior with Cursor provider debug enabled, so I would like to correct my original hypothesis.

    I no longer think the available evidence shows an unnormalized mcp_opencodex-responses_* name reaching Pi as a structured tool call. In control runs, Cursor MCP calls were normalized to the tool names declared by Pi and executed normally.

    The issue was first observed in an already active Pi session, before provider debug was enabled. I then kept that same session and sent 続けて ("continue") to check whether the behavior would recur with the accumulated conversation and tool-history context. The same class of artifact recurred.

    This is therefore a same-session continuation reproduction, not a clean-session minimal reproduction.

    In the reproduced affected turn, Pi recorded:

    • rawStopReason: "stop"
    • zero structured tool-call blocks
    • assistant text containing tool-call-like markup, such as [Tool call: TodoWrite] and [Tool call: Grep]

    The Cursor provider trace indicates that the final tool-continuation response was delivered as normal text deltas, rather than as structured tool-call events.

    Immediately before that continuation, the trace also showed multiple Responses MCP lifecycle frames (partialToolCall / toolCallStarted), followed by an mcpArgs exec frame. OpenCodex then logged client-tool-suspend and started the tool-continuation request.

    I am not claiming that this proves a bridge-lifecycle bug: mcpArgs may be the intended authoritative completion path. I am sharing the observation because the text-only tool-call artifact followed that handoff.

    As a practical workaround, explicitly prompting the model to use Pi's built-in grep tool has avoided the issue in the cases I observed. For example:

    Use Pi's built-in grep tool to search the codebase.

    I do not consider this a root-cause fix, but it may help narrow the condition: normal, explicitly directed Pi tool use was delivered as a structured call in the cases I tested.

    Environment:

    • OpenCodex 2.28.0
    • Pi 0.84.2
    • provider/model: cursor/grok-4.6
    • effort: xhigh
    • inbound protocol: chat

    Sanitized excerpts are included below. The full provider/session logs remain private because they contain project context and tool arguments.

    Sanitized continuation-reproduction summary
    # Sanitized continuation-reproduction summary
    
    ## Scope
    
    - First observation: an already-active Pi session, before Cursor provider debug was enabled.
    - Reproduction: the same Pi session was retained and sent `続けて` (`continue`), preserving its accumulated conversation and tool-history context.
    - This is a same-session continuation reproduction, not a clean-session minimal reproduction.
    
    ## Affected response as recorded by Pi
    
    - `rawStopReason`: `stop`
    - Structured tool calls: `0`
    - The response text contained display-style tool-call markup, including `[Tool call: TodoWrite]` and `[Tool call: Grep]`.
    
    ## Provider-frame sequence (relative order)
    
    1. Cursor emitted Responses MCP lifecycle frames for more than one client-tool call (`partialToolCall` / `toolCallStarted`).
    2. Cursor emitted an `mcpArgs` exec frame.
    3. OpenCodex logged `client-tool-suspend` and began a `tool-continuation` request.
    4. The continuation ended normally, but Pi received text only rather than a structured tool call.
    
    ## Control observation and workaround
    
    In separate control runs, structured Cursor MCP calls were normalized to Pi's declared tool name and executed normally. In observed cases, explicitly prompting the model to use Pi's built-in `grep` tool avoided the artifact:
    
    > Use Pi's built-in `grep` tool to search the codebase.
    
    This is a workaround, not a root-cause conclusion.
    Sanitized Pi affected-turn excerpt
    {
      "provider": "opencodex",
      "model": "cursor/grok-4.6",
      "pi": {
        "rawStopReason": "stop",
        "structuredToolCallCount": 0,
        "assistantTextExcerpt": "[Tool call: TodoWrite] ... [Tool call: Grep] path"
      }
    }
    Sanitized Cursor frame timeline
    {
      "eventsInOrder": [
        {"kind": "interactionUpdate", "update": "partialToolCall", "toolCase": "mcpToolCall", "call": "redacted-call-A"},
        {"kind": "interactionUpdate", "update": "toolCallStarted", "toolCase": "mcpToolCall", "call": "redacted-call-A"},
        {"kind": "interactionUpdate", "update": "partialToolCall", "toolCase": "mcpToolCall", "call": "redacted-call-B"},
        {"kind": "execServerMessage", "exec": "mcpArgs"},
        {"kind": "OpenCodex diagnostic", "event": "client-tool-suspend"},
        {"kind": "OpenCodex request", "turnType": "tool-continuation"},
        {"kind": "interactionUpdate", "update": "turnEnded"}
      ],
      "notes": {
        "mcpToolCallCompletedFramesObservedInCapturedWindow": 0,
        "continuationDeliveredToPiAs": "normal text deltas; no structured tool call"
      }
    }
  6. changed the title [-][Bug]: Pi receives unnormalized `mcp_opencodex-responses_grep` from Cursor/Grok tool calls[/-] [+][Bug]: Cursor client-tool continuation can return tool-call-like assistant text to Pi[/+] on Aug 21, 2026
  7. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Thank you for the careful correction. This new evidence materially changes the triage result.

    The current capture does not show mcp_opencodex-responses_* escaping as a structured downstream tool name. It shows the opposite boundary: Pi recorded rawStopReason: "stop", zero structured tool calls, and tool-call-like markup inside ordinary assistant text after a same-session client-tool continuation.

    I have narrowed the issue title accordingly. I am keeping needs-info because the user-visible failure is credible, but I am not opening or recommending a mapper patch: the existing generic Cursor MCP normalization is working in the control runs, and changing it would not affect text deltas.

    The next useful reproduction is a clean session, or a bounded sanitized event window covering one affected continuation only. Please include, in relative order:

    1. every partialToolCall, toolCallStarted, mcpArgs, and completion/terminal frame for each call id immediately before client-tool-suspend;
    2. the continuation request boundary and the first downstream text/tool event after it;
    3. whether any sibling client-tool call was still open when mcpArgs caused the suspend;
    4. the final Cursor terminal event (turnEnded, Connect END_STREAM, or another close path).

    A redacted sequence with stable placeholder call ids is sufficient; project text and arguments can remain private.

    This is adjacent to the client-tool continuation/terminal work in #2307, but the current evidence does not prove the same cause. I will keep those investigations separate unless the bounded sequence shows the same lifecycle boundary. No source change is justified yet.

  8. added a commit that references this issue on Aug 22, 2026
  9. lidge-jun commented on Aug 22, 2026

    @lidge-jun
    Owner

    Landed via #2341 at 896cb57

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinglanded-via-maintainerOriginal PR closed after landing via a maintainer merge trainneeds-infoWaiting on reporter for a concrete spec or reproductionproviderProvider adapters, OpenAI-compat presets, upstream API quirkstoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions