[Security] Background subagent silently falls back to local machine execution when SSH remote connection degrades

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

When using Cursor Agent over SSH Remote and the SSH connection becomes degraded (shell commands returning empty output despite exit code 0), spawning a background subagent via the Task tool causes the subagent to execute on the local client machine instead of the remote host.

This happens silently — no error, no warning — and the subagent proceeds to read files, modify code, run git operations, and attempt pushes against the local filesystem.

What happened:

  • Parent agent was operating correctly on a remote Linux host via SSH (/home/user/work/repo on remote)
  • Shell commands started returning empty output (SSH degradation)
  • Parent agent spawned a generalPurpose background subagent with explicit remote path in the prompt
  • The subagent resolved its workspace to the local macOS client: /Users/user/.cursor/projects/empty-window/agent-transcripts/...
  • The subagent had full write permissions and was instructed to commit and push — it attempted these on the wrong machine
  • The subagent was searching through whole machine for local copy of repository triggering macos access alerts
  • The subagent found local copy with different branch and different unstaged changes - proceeded to overwrite it

Security impact: A subagent with full write/execute permissions operated on a completely different machine than intended, without user consent or any error indication. This is a trust boundary violation — the subagent could read sensitive local files, modify wrong repositories, or push to wrong remotes.

Steps to Reproduce

  1. Connect to a remote Linux host via Cursor SSH Remote extension
  2. Start an Agent session (e.g. Opus 4.6) with a multi-step task involving shell operations (gh CLI, git, file edits)
  3. Work through multiple tool calls until the SSH connection becomes unstable — observable as shell commands returning empty stdout despite exit code 0
  4. Have the agent spawn a background subagent (Task tool, generalPurpose type, run_in_background: true) with a prompt that explicitly references the remote workspace path (e.g., /home/user/work/repository)
  5. Observe the subagent scanning local machine (macOS) paths (/Users/user/.cursor/projects/empty-window/...) instead of the remote workspace
  6. The subagent proceeds with file read/write/git operations on the local machine

Note: The SSH degradation in step 3 may be timing-dependent. In my case it occurred after ~30+ tool calls in a long session with heavy gh CLI usage.

Expected Behavior

  1. If the SSH remote context cannot be propagated to the subagent, the Task tool should fail with an explicit error — not silently fall back to local execution
  2. A subagent should never gain write access to a different machine than the one the parent agent was operating on
  3. Shell tool should surface SSH connection failures explicitly (returning empty output with exit code 0 masks the real error and makes the parent agent unable to detect the problem)
  4. The user should be notified immediately if any agent/subagent execution context changes from remote to local

Screenshots / Screen Recordings

Operating System

macOS (local)
Linux (remote)

Version Information

Version: 3.3.30
VSCode Version: 1.105.1
Commit: 3dc559280adc5f931ade8e25c7b85393842acf30
Date: 2026-05-09T18:28:42.332Z
Layout: glass
Build Type: Stable
Release Track: Default
Electron: 39.8.1
Chromium: 142.0.7444.265
Node.js: 22.22.1
V8: 14.2.231.22-electron.0
OS: Darwin arm64 25.4.0

For AI issues: which model did you use?

Parent agent: Opus 4.6 (claude-opus-4-6)
Subagent: generalPurpose (default model, inherited from parent)

For AI issues: add Request ID with privacy disabled

Conversation ID: 24c44386-fd74-4501-a57b-094601d14121

(Request ID not available — privacy was not disabled at time of incident. The conversation transcript is available locally for Cursor team inspection if needed.)

Additional Information

Preceding symptoms (SSH degradation signals):

  • Multiple shell tool calls returned empty stdout with exit code 0
  • Affected commands: gh run view, gh run list, git log, ls, echo
  • This happened for several consecutive tool calls before the subagent was spawned

Subagent invocation details:

  • Tool: Task with subagent_type: "generalPurpose", run_in_background: true
  • Prompt explicitly contained info in format of: “Repository: /home/user/work/repo on branch branchname”
  • The subagent was looking for similar paths on local machine

Related existing forum reports (similar but less severe):

None of these describe the full scenario: a background subagent silently executing its entire tool chain with write permissions on the local machine.

Suggested mitigations:

  1. Subagent spawning must verify it inherits the same execution context (local vs SSH remote) as the parent
  2. If remote context cannot be established, Task tool must return an error
  3. Shell tool must not mask SSH failures as empty-output success
  4. Consider a hard workspace boundary check that blocks any agent from operating outside its configured root

Does this stop you from using Cursor

No - Cursor works, but with this issue

+1, hitting a related but distinct failure mode on the same Cursor build you reported on (3.3.30, commit 3dc559280adc5f931ade8e25c7b85393842acf30). Same local/remote architecture (macOS → Linux EC2 via Cursor Remote-SSH, Opus 4.5 parent agent).

In my case the subagent itself executes remotely correctly — the PARENT’s shell routing flips to local after the subagent returns.

Concrete sequence:

  1. Parent spawns a background subagent (“Launched. Report will be at…”)
  2. Subagent writes scratch/mr-reviews/<n>/charter-audit-v3.md and returns a summary
  3. I verified the report file is on the EC2, not on the local Mac (local has no scratch/mr-reviews/ tree at all — it would have had to be created from nothing)
  4. Parent receives summary, attempts a follow-up Shell call, reports “Having trouble accessing files”
  5. Asked parent for its current cwd — it returns /Users/<my-mac-username>/... (Mac path)
  6. One or two tool calls later, cwd flips back to /home/ubuntu/repos/<repo> and the agent itself reports “shell state was stale”

Differences from your repro:

  • No empty-stdout / exit-0 SSH-degradation precursor in my sessions
  • Subagent’s own execution was correct; the parent was the wrong-machine actor on follow-up

Security implication: Same trust-boundary class as your report. The parent agent has full read/write/exec permissions and silently operates on the wrong machine after the subagent returns. If asked to act on the audit findings (commit, push, modify files, run scripts), those operations would land on the local Mac instead of the EC2 — wrong-machine git ops, reads of sensitive local files, modifications to whatever local repo clones happen to exist. Exposure surface is equivalent to the subagent-fallback case you described; only the trigger differs.

Workaround: explicit working_directory=<remote absolute path> on every Shell call appears to pin routing. Quit + restart reliably re-anchors.

Possible underlying model (inferred from observed behavior — I don’t have visibility into Cursor’s internals): the pattern is consistent with the agent running structurally local. File reads/writes route through Remote-SSH; Shell tool calls and subagent orchestration default to local unless remote anchoring is explicitly maintained on each call. Under this read, the failure isn’t “the agent forgot where it is” — the agent is always-local, and remote anchoring has to be re-applied per call. Any code path that drops it (SSH degradation, subagent spawn, subagent return, prompt-driven shell calls without explicit cwd) produces silent local execution. That would explain why the workaround is per-call working_directory, why the failure can flip mid-session, and why your repro (subagent fallback under degradation) and mine (parent state corruption after subagent return) produce the same outcome through different triggers. If this model is accurate, the structural fix is enforcing remote anchoring on every shell-routed operation — or raising explicit errors when it can’t be applied — rather than propagating context on subagent spawn alone.

Recurring since the forced migration off ms-vscode-remote.remote-ssh a couple weeks ago.

Version: 3.3.30 (Universal)
VSCode Version: 1.105.1
Commit: 3dc559280adc5f931ade8e25c7b85393842acf30
Local: macOS 15 Sequoia (Darwin 24.6.0, arm64)
Remote: Ubuntu 24.04.4 LTS (kernel 6.17.0-1013-aws), EC2
Parent agent model: Opus 4.5

Conversation IDs available on request.

This is a known bug that our team is actively investigating and working to fix. The root cause has been identified. A fix is in progress that pins the workspace context to an immutable identity per chat session, which should prevent the local fallback entirely.

Workaround until the fix ships: On every Shell call, explicitly set working_directory to the remote absolute path (e.g., /home/user/work/repo). This forces remote routing on each call. If the session becomes unstable (empty stdout with exit code 0), quitting and restarting Cursor reliably re-anchors the remote context.

@gus-at-fsp — thanks for the additional data point. Your variant (parent shell context flipping after subagent returns) is part of the same underlying issue. The same workaround applies.

Let me know if you run into anything else.

+1, still hitting this on a newer cursor-server build than the original reports, with a new and more dangerous symptom that I think belongs in the same fix. Posting EC2-side telemetry the previous reporters (Mac-side only) couldn’t capture, in case it helps the team verify the workspace-identity-pinning fix covers all variants.

Environment

Local client macOS (Cursor desktop)
Remote Ubuntu 24.04 EC2 over Cursor Remote-SSH (custom SSH port)
Workspace Multi-root *.code-workspace with 14 folders (added the 14th yesterday — a strong trigger, see “Two project identities” below)
Active cursor-server commit on remote 0cf8b06883f54e26bb4f0fb8647c9500ccb43310 (one generation newer than the original reports’ 3dc559280adc…; that build is now orphan on the same remote)
Parent agent model Claude Opus 4.6
Frequency ≥ 3 occurrences today alone, sub-hour intervals; ≥ 10 across the last 3 days

Mohit’s announced workaround (explicit working_directory on every Shell + quit/restart) is in effect and works for ~5–10 fresh dispatches after a restart, then drifts.

New symptom not in either prior report: cross-project MCP leak

Szymon’s repro: subagent lands in /Users/[me]/.cursor/projects/empty-window/.... Gus’s repro: parent shell flips to /Users/[me]/... after subagent returns.

What I’m seeing today is worse. When the agent panel drifts to the local Mac context, the operator’s personal Postgres MCP (call it user-postgresql) is routed to a different Cursor project’s database — the database registered under another open project on the same Mac (call it Project-B, a completely unrelated workspace). The drifted subagent gets SELECT 1 returns from Project-B’s DB MCP while believing it’s talking to Project-A’s postgres MCP. From the agent’s side, the only signal is that local Project-A’s MCP starts timing out — but only because its requests are being silently answered by Project-B’s MCP daemon, which is happily online.

This is a trust-boundary violation that extends beyond filesystem fallback: a subagent with full MCP write capabilities could land in a different tenant’s database without any error, warning, or empty-window indicator. The [Security] tag on this thread is correct; if anything the cross-project MCP class makes it more severe than the filesystem case the team is currently scoped to.

The drifted agent in my case caught it itself and aborted (we have a defensive Task-wrapper rule that runs hostname && pwd && whoami && SELECT 1 at the top of every subagent prompt). Without that guardrail, the wrong DB would have been written to.

Suggested addition to the announced workspace-identity-pinning fix:

  • MCP routing must inherit the same immutable workspace identity as the workspace-path binding. Pinning the workspace path while leaving MCP resolution to “whichever Cursor project is currently focused on the Mac” still leaks across projects.

  • Consider an explicit MCP-identity assertion on every MCP request (return an error if the MCP daemon’s project identity doesn’t match the agent’s workspace identity).

EC2-side telemetry (the bit Mac-side users can’t see)

The remote ~/.cursor-server/data/logs/[session]/remoteagent.log shows a consistent dual-channel pattern on every IDE reattach — two ManagementConnections + two ExtensionHostConnections within a 1–3 s window:

13:40:14.403 [info] [ManagementConnection] New connection established.

13:40:14.514 [info] [ExtensionHostConnection] New connection established.

13:40:15.496 [info] [ManagementConnection] New connection established. ← second within 1.1 s

13:40:15.626 [info] [ExtensionHostConnection] New connection established. ← second within 1.1 s

Paired ext-host process dirs are created at every reattach (e.g. exthost9/ and exthost10/ both at 13:40), each with its own Cursor Agent Exec activation. In the first ~3 s after each reattach, remoteagent.log records 1500+ RequestStore#acceptReply was called without receiving a matching request warnings with duplicate request IDs (e.g. request 1525 ... request 1525) — the textbook signature of two RPC channels competing for the same request-ID space. The flood subsides only after the second channel finishes handshaking. The hypothesis is that the agent-panel WebView runs in a separate connection from the workbench, and when the agent-panel side disconnects independently (Mac sleep, WebView reload, .code-workspace edit), the agent panel’s “remote-attached?” flag flips to false while the workbench’s stays true — exactly the silent-fallback condition described in the original report.

This lines up with Mohit’s “workspace context not pinned to an immutable identity per chat session” diagnosis. The duplicate-ID flood is a separate channel-de-duplication bug worth fixing in its own right.

Two project identities for the same workspace

On the remote, both project-identity dirs exist for the same logical workspace:

$ ls ~/.cursor/projects/

home-ubuntu-project-a ← folder identity

home-ubuntu-project-a-code-workspace ← workspace identity (same workspace, different hash)

home-ubuntu ← parent-folder identity

tmp-[uuid1]/ tmp-[uuid2]/ tmp-[uuid3]/ tmp-[uuid4]/ ← four ephemerals

.code-workspace was edited yesterday to add a 14th folder; cursor_agent_exec startup logged workspacePathCount: 13 → 14 between two consecutive exthost activations. If the IDE’s workspace-identity hash key includes the folder list, that edit alone could explain why the Mac client briefly fails to recognise the prior remote agent runtime as “the same workspace” and routes new dispatches to a fresh local empty-window. Reconciling these to one canonical identity (regardless of folder-vs-workspace open mode and folder-list edits) is, I think, what Mohit’s announced fix is doing — confirming it covers folder-list edits as a trigger would be valuable.

Orphan cursor-server installs

$ ls ~/.cursor-server/bin/linux-x64/

0cf8b06883f54e26bb4f0fb8647c9500ccb43310 ← active (May 15)

3dc559280adc5f931ade8e25c7b85393842acf30 ← orphan from May 10 (the build the original reports filed against)

7f0f522221d0ba220e4edb766bb3c47c08c14ab0 ← orphan from May 7

80b138a7a0a948e1a798e9ed7867d76a1ba9a310 ← orphan from May 8

93e603f703cd553a6bb3644711a3379bbbb31180 ← orphan from May 14

Five generations of cursor-server are co-resident on the EC2; each previous one still holds (at minimum) a playwright MCP daemon as an orphan process. Independent of the routing bug but worth noting: it bloats the remote and makes confirming “which version is responsible for what” harder during regression testing. A simple TTL or version-pin sweep on the remote install dir would clean this up.

Cursor-agent-worker gate

Every exthost*/anysphere.cursor-agent-worker/Cursor Agent Worker.log on the remote reads:

[info] [cursor-agent-worker] Activating

[info] [cursor-agent-worker] Disabled by feature gate “enable_cursor_agent_worker_extension”

Worker is gated off on the remote; only cursor-agent-exec activates (with a 4.4 s activation latency on the latest exthost). Unclear whether intentional (worker may be cloud-only) or whether the 4–5 s bootstrap window between exec activations is where the Mac side falls back to local. Flagging in case it’s relevant.

Net ask

Confirm the in-flight fix covers:

  1. MCP-routing identity (not just filesystem / working-directory). Today’s repro shows the leak is no longer confined to empty-window — it can route a subagent’s MCP to another project’s daemon.

  2. .code-workspace folder-list edits as a trigger, not just sleep / SSH-degradation.

  3. Surfacing the dual-channel state in some user-visible way at dispatch time. Knowing which project context a subagent will run in (e.g. showing ~/.cursor/projects/home-ubuntu-project-a vs home-ubuntu-project-a-code-workspace vs empty-window in the dispatch UI) would let users like us catch drift without per-dispatch guardrails.

Happy to share more detail or repro logs on request — I have remoteagent.log excerpts, the duplicate-request-ID flood logs, and Conversation IDs for the recurrences today. Privacy mode was on at the time so no Cursor-side Request IDs unfortunately; can disable it for the next repro if useful.

Sergey, thanks for the detailed report.

What’s happening: The core fix for this issue shipped in 3.4.20, but a second hardening fix that fully pins agent execution routing (including MCP) hasn’t reached stable yet and expected in the next stable build within days.

This hardening fix addresses exactly the drift pattern you’re seeing, including the cross-project MCP routing, since MCP tool execution flows through the same workspace-pinned session as shell and file operations.

What to do now: Keep your defensive Task-wrapper rule in place. When the next stable build ships, update and test — the working_directory drift after 5-10 dispatches should stop. I’ll post here when it’s out.

Your EC2-side telemetry (dual-channel pattern, duplicate request IDs) and the .code-workspace folder-edit trigger are noted and added to the tracking ticket.

Hey @Szymon_Strojniak,
The core fix for the subagent local fallback issue has been shipped. Updating to the latest version should resolve this — could you give it a try and let me know if you’re still seeing the fallback behavior?

cc @gus-at-fsp @lincos