**Local agent shell tool call hangs indefinitely — never completes, declared timeout not enforced (bridge server 1.0.0)**

Summary

When running a local agent via the Cursor Agent SDK (Python SDK 1.0.24 → cursor-sdk-bridge node process), a shell tool call intermittently gets stuck in running status forever. The tool result is never delivered, the agent-declared timeout (30s) is never enforced, and the model stream goes completely silent because it is waiting for the tool result. The bridge process itself stays healthy — its HTTP API keeps responding — only the shell tool execution channel appears to deadlock.

Environment

  • cursor-sdk (Python 1.0.24) with bundled cursor-sdk-bridge, bridge server_version=1.0.0
  • OS: Linux (Azure VM, Ubuntu), local ext4 filesystem
  • Node: bundled/runtime reporting (node:PID) ExperimentalWarning: SQLite is an experimental feature
  • Agent options: AgentOptions(api_key=..., model=..., local=LocalAgentOptions(cwd=...)) — no custom permission config

Expected behavior

A shell tool call either completes, fails, or is killed after its declared timeout, and a terminal tool result event is always delivered to the run stream.

Actual behavior

The shell tool call enters running and never produces a result. Concrete trace from the persisted run event store (sdk-agent-store/index.db, run run-f9b5ae33-aaff-4944-9078-b492caff919a, agent agent-ac9e8bce-2174-41ca-b550-7fcbd6fbcb3c, 2026-07-28):

  • seq 300, 08:35:24.579Ztool_call shell status=running args={"command":"find src -type f | sort","timeout":30000}
  • seq 301–306, 08:35:24.580–24.680Z — only updateTodos events follow (last one status=completed), proving other tools kept executing normally after the shell call hung
  • Then zero events for 600 seconds until our client-side watchdog cancelled the run at 08:45:25.036Z (status=CANCELLED was persisted immediately)

Key facts that rule out environmental causes:

  • The same command run manually in the same working directory completes in ~1ms (21 files, local ext4, no symlinks/special files)
  • Three earlier shell calls in the same run completed normally in 300–500ms
  • After 600s of silence, SdkAgentService/CancelRun, CloseAgent and SdkBridgeControlService/Shutdown all returned HTTP 200 instantly — the bridge’s RPC layer was alive the whole time; only the shell tool execution path was stuck
  • The agent-specified timeout: 30000 was never enforced

Frequency

4 occurrences within ~9 hours on 2026-07-28 (04:01, 04:45, 07:57, 08:45 UTC), all with the same signature. A second affected run: run-41594c68-74a4-4de6-b1af-c0d2e3952d4b — shell cd <workspace> && ... went running at 04:35:02.218Z and never completed, while a concurrent read tool completed 225ms later.

Suspected area

The bridge’s shell tool execution path (persistent shell session completion detection / child-process pipe handling) deadlocks, below the layer that would enforce the declared timeout.

Impact

Every occurrence silently stalls the whole agent run; without a client-side idle watchdog the run would hang forever. Happy to provide the full run event export or the persisted sdk-agent-store for the affected runs.

Hi there!

We detected that this may be a bug report, so we’ve moved your post to the Bug Reports category.

To help us investigate and fix this faster, could you edit your original post to include the details from the template below?

Bug Report Template - Click to expand

Where does the bug appear (feature/product)?

  • Editor, Tab & Chat (autocomplete, Composer, in-editor agent)
  • Terminal & commands
  • Models, pricing & API keys (availability, Auto/Max, BYOK/Bedrock)
  • MCP & tools
  • Cloud Agents & Automations (cursor.com/agents, scheduled/event)
  • BugBot & Code Review
  • Cursor CLI
  • Cursor Mobile
  • Remote (SSH / Dev Containers / WSL)
  • Account, billing & login
  • Something else…

Describe the Bug
A clear and concise description of what the bug is.


Steps to Reproduce
How can you reproduce this bug? We have a much better chance at fixing issues if we can reproduce them!


Expected Behavior
What is meant to happen here that isn’t working correctly?


Screenshots / Screen Recordings
If applicable, attach images or videos (.jpg, .png, .gif, .mp4, .mov)


Operating System

  • Windows 10/11
  • MacOS
  • Linux

Version Information

  • For Cursor IDE: Menu → About Cursor → Copy
  • For Cursor CLI: Run agent about in your terminal
IDE:
Version: 2.xx.x
VSCode Version: 1.105.1
Commit: ......

CLI:
CLI Version 2026.01.17-d239e66

For AI issues: which model did you use?
Model name (e.g., Sonnet 4, Tab…)


For AI issues: add Request ID with privacy disabled
Request ID: f9a7046a-279b-47e5-ab48-6e8dc12daba1
For Background Agent issues, also post the ID: bc-…


Additional Information
Add any other context about the problem here.


Does this stop you from using Cursor?

  • Yes - Cursor is unusable
  • Sometimes - I can sometimes use Cursor
  • No - Cursor works, but with this issue

The more details you provide, the easier it is for us to reproduce and fix the issue. Thanks!

Hey, thanks for the super detailed report. The traces and run ID help a lot.

A couple clarifications on what’s actually happening here. This isn’t a deadlock in the shell tool in the bridge. It’s a broader issue with delivering events back to the SDK. We checked your runs. The shell commands did run and completed successfully, and the whole turn finished on the backend. What got stuck is sending and saving the tail end of the event stream back to the SDK client. So run.cancel() after a long silence is canceling work that already finished, and the tokens for it were already charged.

Practical takeaways:

  • Instead of re-running, try resume or fetch state for the stuck run. You might recover more that way than with a re-run.
  • The declared shell timeout: 30000 limits the command execution on the client, not event delivery back to the SDK. So it not firing here is a symptom, not the root cause.
  • Your out-of-process watchdog is exactly the interim pattern we recommend for cases like this. Keep it.

Since you’re on an Azure VM, we’ve seen similar cases where the stream stalled due to idle eviction in the transport. It’s worth checking egress with TCP keepalive enabled.

To dig deeper, taking you up on your offer, please send stderr from the bridge process. It should show warnings if the stream stalled or retried. Also send an export of sdk-agent-store for the affected runs run-f9b5ae33-... and run-41594c68-.... That will help us separate which mechanism actually fired in your case.

This is a known issue and we’re tracking it. I can’t share a fix timeline yet, but I’ll post an update in the thread as soon as I have one.

Thanks — that clarification helps a lot. We’ll treat this as an event-stream delivery / persistence stall, not a hung shell, and keep the out-of-process idle watchdog.

Actions on our side

  1. We’ll change our stall handling to prefer resume / fetch run state before treating the job as failed or starting a full re-run (to avoid canceling work that already finished and was already billed).
  2. TCP keepalive on the Azure VM: we set
    tcp_keepalive_time=60, intvl=30, probes=5 (persisted).
    However, during live stalls the bridge’s outbound sockets to Cursor producer hosts do not show SO_KEEPALIVE / timer:(keepalive,…) — only other processes do. So sysctl alone may not cover the bridge transport unless the bridge enables keepalive on those sockets. Happy to try any other egress guidance you recommend for Azure SNAT idle eviction.
  3. Attached: cursor-support-lite.tgzsdk-agent-store index.db for the workspaces containing run-f9b5ae33-… and run-41594c68-…, plus JSON exports of run_events for those runs (and a few related stalls). Local store status for these is CANCELLED because our watchdog called cancel() after ~600s of SDK silence.

Bridge stderr
For the past stalls we only retained a short stderr tail from our diagnostics (mainly the Node SQLite ExperimentalWarning). We did not capture a full bridge stderr log with stream stall/retry warnings. On the next reproduction we’ll capture full bridge stderr and send it over.

Please let us know if you also want the larger full-store tarball, or a specific logging flag/env to make stall/retry warnings more visible in bridge stderr.

Thanks,

cursor-support-lite.zip (1.7 MB)

Thanks. I went through the whole export, and it answers several open questions.

The main one is your note about the missing SO_KEEPALIVE on the bridge outbound sockets. That is the key. The stalls line up cleanly with a gap of 4+ minutes of inactivity on the shell channel, and 4 minutes is the default idle timeout for Azure SNAT or load balancer. During a stall the connections stay in ESTAB with a non zero Send-Q and no keepalive timer, which is the classic sign of a flow getting evicted by SNAT due to idleness. This confirms it is about event delivery through the transport, not the shell itself. The under 1 minute exceptions in your data are npm run build and tsc, where the build runtime keeps the channel quiet past the same 4 minutes.

A couple clarifications that follow from this:

  • You are right that sysctl keepalive on the VM does not help here. The bridge does not set SO_KEEPALIVE on those sockets, so OS level settings do not apply. The keepalive or heartbeat needs to be implemented in the bridge transport itself, and that is on our side.
  • Your changes on your side are the right approach. Prefer resume or fetching run state before cancel, and keep the idle watchdog. That reduces the chance of killing work that already finished and was already billed.

One small note about the export itself. ef2e224e-index.db was copied without its WAL, so by itself it looks stale. No need to resend anything. The full data is in index.db plus index.db-wal. For future exports, copy the store together with its -wal and -shm files.

What would still help is the full bridge stderr on the next repro. It will show if the bridge logged a stream stall or retry warning and will confirm which leg of the transport dies. A reliable way to trigger it based on your data is to run a shell call, keep the run busy with non shell tools for 5+ minutes, then run another shell call. The second one should stall with high probability. Getting stderr from that would be great.

This is a known issue and we are tracking it. I cannot share a fix ETA yet. I will post an update in the thread as soon as I have one.