Skip to content

fix: renderer crash on empty pipelines, Cursor tool-schema rejection, dead SDK update - #648

Open
playtix-brain wants to merge 2 commits into
SDSLeon:masterfrom
playtix-brain:fix/upstream-bugs
Open

fix: renderer crash on empty pipelines, Cursor tool-schema rejection, dead SDK update#648
playtix-brain wants to merge 2 commits into
SDSLeon:masterfrom
playtix-brain:fix/upstream-bugs

Conversation

@playtix-brain

Copy link
Copy Markdown

Three independent fixes, each with a regression test. All reproduced on 1.6.5/1.6.6 before fixing.

1. Renderer crash: TypeError: e is not iterable

parseHeadFileView indexed parts[parts.length - 1] without guarding an empty pipeline. A blank or pipe-only command (bash -c '', cd x && , |) makes splitShellPipeline return [], so splitShellWords(undefined) iterates undefined and takes down the whole renderer — the React error boundary replaces the app shell. It is reached from ordinary tool-call rows, so one odd command from an agent kills the UI.

2. Cursor turns fail with NonRetriableError: Provider Error

spawn_agent and wait_for_agent declared oneOf at the root of their inputSchema. Cursor's backend rejects root-level unions and fails the entire turn, for every model (reproduced with Fable 5, Sonnet 5 and Kimi K3). Verified outside the app with cursor-agent acp plus a minimal HTTP MCP server mirroring these tools: with oneOf the turn errors, without it the same prompt answers normally. The prompt/tasks and run_id/run_ids choices stay documented in the tool descriptions and are already enforced by the request parser.

3. Cursor SDK update action was dead

cursorSdkUpdateCommand/canUpdateCursorSdk only accepted global-npm/global-pnpm, which sdkPackageDiscovery reports only from the deferred npm root -g probe — and that probe never runs once a filesystem candidate matched. A Node prefix of its own (~/.local, nvm, fnm, volta, Homebrew) resolves as global-inferred, so no command was produced and the click fell through to the agent updater, refreshing the CLI while @cursor/sdk stayed on its old version. The update banner then never cleared.

Tests: 177 passing across the touched modules; typecheck clean on the changed files.

🤖 Generated with Claude Code

- chat: guard empty shell pipelines in parseHeadFileView. A blank or pipe-only
  command made splitShellPipeline return [], so parts[parts.length - 1] handed
  undefined to splitShellWords and the renderer died with "e is not iterable".
- crossagents: drop the root-level oneOf from spawn_agent/wait_for_agent. Cursor's
  backend rejects union-typed tool schemas and fails the whole turn with
  "NonRetriableError: Provider Error", for every model.
- cursor: allow updating a globally installed @cursor/sdk. The update command only
  accepted global-npm/global-pnpm, which the discovery only reports from the
  deferred npm root -g probe; a Node prefix of its own resolves as global-inferred,
  so the action was dead and the agent updater refreshed the CLI instead.

Each fix ships a regression test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@playtix-brain
playtix-brain requested a review from SDSLeon as a code owner August 29, 2026 02:14
The global keydown listener forwards every event to eventToKeybinding, and
synthetic or stand-in events can arrive without `key`. normalizeKeyPart called
`part.toLowerCase()` unguarded, so one such event threw inside a listener that
has no error boundary and replaced the whole app shell with the crash screen
("Cannot read properties of undefined (reading 'toLowerCase')").

Normalizing an absent key now yields undefined, which simply matches no
keybinding. Adds a regression test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@SDSLeon SDSLeon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Requesting changes for one blocking protocol-correctness issue. Removing the root-level unions is appropriate for Cursor compatibility, but the either/or contract must still be enforced at runtime so malformed model calls cannot silently select the wrong operation.

{ type: "object", required: ["prompt"] },
{ type: "object", required: ["tasks"] },
],
// No root-level union here: Cursor's backend rejects tool schemas that carry

@SDSLeon SDSLeon Aug 29, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The root-level oneOf removal fixes Cursor compatibility, but the new comment says the request parser still enforces the choice and it currently does not.

  • With both prompt and tasks, spawnAgent takes the tasks branch and silently discards prompt.
  • With both run_id and run_ids, dispatch waits only for run_ids and silently ignores run_id.

Because the published schema now permits both shapes, a model-generated call can spawn or wait on the wrong work. Please keep the schema union-free, add explicit exactly-one validation before branch selection for both pairs, and add regression tests that pass both conflicting fields, assert an isError result, and verify that no spawn/wait manager method was called.

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.

2 participants