Repository navigation
[Bug]: Cursor client-tool continuation can return tool-call-like assistant text to Pi #2305
Description
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
-
Run OpenCodex on loopback and configure Pi with the generated
opencodexprovider usingapi: "openai-completions". -
Start Pi with the Cursor-routed Grok model and allow only
grep:pi --model opencodex/cursor/grok-4.6:high --tools grep --no-session
-
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 -
In the same Pi session, ask it to use the Cursor-side long display alias for the only available
greptool (or wait for Cursor to choose that display form). -
Pi receives the following instead of its declared tool name:
[TOOL_CALL]mcp_opencodex-responses_grep[ARGS]{"pattern":"OpenCodex"} -
No normal
grepexecution/result follows. The turn is effectively blocked because Pi cannot dispatch a tool namedmcp_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
- [Bug] Cursor adapter: false "Shell/Read blocked" reports caused by shell-tool name mismatch + native-exec rejections #399 is related in that it handles Cursor tool-name aliasing for the shell bridge, but it concerns
shell_command/exec_commandand native-exec rejections. It does not cover Pi receivingmcp_opencodex-responses_grepas a tool name. - I searched the issue tracker for
mcp_opencodex-responses,opencodex-responses_grep, Pi/Cursor tool calls, and Cursor tool aliases before filing.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
Ingwannu commentedon Aug 21, 2026
Thanks for the concrete reproduction. I checked the exact Cursor MCP event shape against both
v2.28.0and currentdev@c0cbe494e.The ordinary structured path already normalizes generic tools, not only the shell bridge. With a request catalog containing only
grep, a CursormcpToolCallwhose provider isopencodex-responsesand whose tool name ismcp_opencodex-responses_grepmaps to this downstream event sequence:I added and ran the exact
grepregression 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:
toolCallStarted,partialToolCall, andtoolCallCompletedcases;tool_calls[].function.nameentry or plain assistant content;/healthzversion.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 thisneeds-inforather than closing it because the user-visible failure is credible, but the currently claimed mapper path does not reproduce.lidge-jun commentedon Aug 21, 2026
리뷰 · 우선순위 58 / 80
지금
devHEADc0cbe494e에서 리포터가 가리킨 매퍼는 이미 접두를 접음.src/adapters/cursor/tool-definitions.ts:333-336CURSOR_MCP_DISPLAY_PREFIX = mcp_opencodex-responses_.normalizeCursorWireName이 그 prefix를 slice.src/adapters/cursor/protobuf-events.ts:278-281mcpWireNameFromArgs가toolName||name에 그걸 탐.recordToolCall:1100-1116이 advertised catalog 이름으로openToolCalls.name을 고정.commitToolCall이 그 이름으로tool_call_start를 냄. 셸 브릿지만의 특수 케이스가 아님. genericgrep도 같은 함수. 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_callitem.name에서 복사. 매퍼가grep을 내면 Pi도grep을 받음. 반대로 긴 알리아스가 structured로 새면src/bridge.ts:1041-1046declaredToolNames가드가 502undeclared client tool. 리포터 HTTP 200. 그 가드랑 안 맞음. 긴 이름이 structuredtool_calls[].function.name으로 나갔으면 200이 아님. 그래서 트랜스크립트[TOOL_CALL]mcp_opencodex-responses_grep[ARGS]{"pattern":"OpenCodex"}만으로는 경계를 못 가름. Pi가 컨텐트 텍스트를 그렇게 그리는지, Chattool_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_NAMESGrep/grep힌트(tool-definitions.ts:21-24)는 카탈로그에 없을 때 쓰지 말라는 문구. 매퍼 구멍으로 단정 금지.리포터 버전 2.28.0. 현재
dev는 v2.29.0 태그 + Bun 1.4.0 + Ox Alphax-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 Completionschoices[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이 작성했습니다
knttkd commentedon Aug 21, 2026
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"[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 anmcpArgsexec frame. OpenCodex then loggedclient-tool-suspendand started the tool-continuation request.I am not claiming that this proves a bridge-lifecycle bug:
mcpArgsmay 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
greptool has avoided the issue in the cases I observed. For example: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:
2.28.00.84.2cursor/grok-4.6xhighchatSanitized excerpts are included below. The full provider/session logs remain private because they contain project context and tool arguments.
Sanitized continuation-reproduction summary
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" } }[-][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[/+]Ingwannu commentedon Aug 21, 2026
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 recordedrawStopReason: "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-infobecause 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:
partialToolCall,toolCallStarted,mcpArgs, and completion/terminal frame for each call id immediately beforeclient-tool-suspend;mcpArgscaused the suspend;turnEnded, ConnectEND_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.
Merge pull request #2341 from lidge-jun/codex/cursor-2305-text-marker
fix(cursor): fold display aliases in textual pseudo tool-call markers…
lidge-jun commentedon Aug 22, 2026
Landed via #2341 at 896cb57
fix(cursor): quarantine textual TOOL_CALL markers off the text channel (
fix(cursor): fold display aliases in textual pseudo tool-call markers…
Merge pull request lidge-jun#2341 from lidge-jun/codex/cursor-2305-te…