-
Notifications
You must be signed in to change notification settings - Fork 864
fix(cursor): unknown exec replies with ExecClientThrow + streamClose instead of silence #2322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -253,12 +253,39 @@ describe("Cursor native exec bridge", () => { | |
| } | ||
| }); | ||
|
|
||
| test("unknown exec cases return empty reply instead of throwing (#116 hardening)", async () => { | ||
| test("unknown exec cases reply with ExecClientThrow + streamClose instead of silence (T05)", async () => { | ||
| const result = await handleCursorNativeExec(execMessage({ | ||
| case: undefined, | ||
| value: undefined, | ||
| })); | ||
| expect(result).toEqual([]); | ||
| // T05 (senpi contract): a frame that cannot be answered gets a typed in-band error | ||
| // + stream-close so the server unblocks with a known failure. #116 was about an | ||
| // unhandled throw propagating to failAndClear and killing the whole gRPC connection; | ||
| // a typed ExecClientThrow does not do that. | ||
| expect(result).toHaveLength(2); | ||
|
|
||
| // Control messages use a different top-level case; decode them directly from the wire. | ||
| const throwMsg = fromBinary(AgentClientMessageSchema, result[0]); | ||
| const closeMsg = fromBinary(AgentClientMessageSchema, result[1]); | ||
| expect(throwMsg.message.case).toBe("execClientControlMessage"); | ||
| if (throwMsg.message.case === "execClientControlMessage") { | ||
| expect(throwMsg.message.value.message.case).toBe("throw"); | ||
| if (throwMsg.message.value.message.case === "throw") { | ||
| expect(throwMsg.message.value.message.value.error).toContain("Unknown exec message variant"); | ||
| } | ||
| } | ||
| expect(closeMsg.message.case).toBe("execClientControlMessage"); | ||
| if (closeMsg.message.case === "execClientControlMessage") { | ||
| expect(closeMsg.message.value.message.case).toBe("streamClose"); | ||
| } | ||
| }); | ||
|
|
||
| test("unknown exec cases do NOT kill the gRPC connection (#116 hardening preserved)", async () => { | ||
| // The T05 typed reply must not propagate into failAndClear. The transport-level | ||
| // contract is that handleCursorNativeExec returns bytes (not throws), which is | ||
| // what live-transport writes back. This test pins that boundary. | ||
| const replies = await handleCursorNativeExec(execMessage({ case: undefined, value: undefined })); | ||
| expect(replies.length).toBeGreaterThan(0); | ||
|
Comment on lines
+283
to
+288
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
rg -n -C 8 '\bfailAndClear\b|\bhandleCursorNativeExec\b' src tests || trueRepository: lidge-jun/opencodex Length of output: 50376 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- target test ---'
sed -n '240,315p' tests/cursor-native-exec.test.ts
printf '%s\n' '--- handler dispatch ---'
rg -n -C 12 'function handleCursorNativeExec|const handleCursorNativeExec|export async function handleCursorNativeExec|unknown exec|ExecClientThrow|streamClose' src/adapters/cursor/native-exec.ts tests/cursor-native-exec.test.ts
printf '%s\n' '--- transport native-exec path ---'
sed -n '1265,1305p' src/adapters/cursor/live-transport.ts
printf '%s\n' '--- transport tests and connection assertions ---'
rg -n -C 5 'unknown|native exec|ExecClientThrow|streamClose|connection|live transport|LiveTransport' tests --glob '*.test.ts' | head -n 400Repository: lidge-jun/opencodex Length of output: 48298 Add transport-level coverage for the gRPC liveness claim.
🤖 Prompt for AI Agents |
||
| }); | ||
|
|
||
| test("rejects native write and delete when apply_patch is available", async () => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Assert the original message ID in both regression tests.
The first test validates the message cases and error text, but it does not validate
throw.idorstreamClose.id. The second test only checks that at least one reply exists. A wrong or default ID would pass both tests and break response correlation. Store theexecMessage(...)result and assert each response ID equalsexecMsg.id; decode the first reply in the second test if it remains separate.Also applies to: 283-288
🤖 Prompt for AI Agents