Cloud Agent RecordScreen SAVE_RECORDING fails with kill ESRCH after ffmpeg already exited; polished demo never runs

Bug: RecordScreen SAVE_RECORDING fails with kill ESRCH after ffmpeg has already exited, so polished demo (cursor overlay) never runs

Summary

On a Cloud Agent VM, RecordScreen START_RECORDING captured a complete desktop proxy MP4. When the agent later called SAVE_RECORDING, the executor tried to SIGTERM ffmpeg, the PID was already gone, and SAVE failed with:

Failed to send SIGTERM: kill ESRCH

A retry then returned:

No active recording to save

The raw proxy file was already a valid, faststart-finalized MP4 on disk. The save path never reached renderPolished, so there is no official demo artifact and no composited mouse cursor.

This looks like a robustness bug in LocalRecordScreenExecutor (exec-daemon record-screen): it treats “ffmpeg already exited” as a hard failure even when the proxy file is complete. ffmpegDiedEarly is recorded on the child exit handler but is not used to skip SIGTERM and continue polishing.

Incident (this run)

  • Cloud Agent run ID: bc-14f0acfe-baba-4673-b490-399ebaf43431
  • Date: 2026-09-14 (UTC)
  • VM: Cursor Cloud Agent Linux desktop (TigerVNC / AnyOS), --record-screen-enabled, --computer-use-enabled
  • Display: 1920x1200 @ 96 DPI, :1
  • Product under test: a local web app in Chrome (details omitted)

Reproduction (generic)

  1. Start a Cloud Agent with computer use and RecordScreen enabled.
  2. Open a GUI (e.g. Chrome) on the agent desktop.
  3. Call RecordScreen with mode=START_RECORDING.
  4. Drive the UI with the computerUse subagent for several minutes (this incident: ~8.5 minutes of clicks/typing).
  5. Immediately after computer-use returns, call RecordScreen with mode=SAVE_RECORDING and a save_as_filename.

Expected: SAVE stops ffmpeg if needed, writes recording-data.json, runs polished rendering (large cursor overlay), and copies recording_demo.mp4 (or the named file) into /opt/cursor/artifacts.

Actual: SAVE fails with Failed to send SIGTERM: kill ESRCH. Retry: No active recording to save. Staging still has only the raw proxy. No polished demo. No official cursor overlay.

Errors (verbatim)

First SAVE:

Failed to send SIGTERM: kill ESRCH

Second SAVE (same turn, after the first failure):

No active recording to save

No OOM in dmesg. Disk had hundreds of GB free. Memory was not exhausted (≈15 GiB RAM, several GiB still available).

Capture that did succeed

Staging session (name redacted to the pattern only):

/opt/cursor/recording-staging/session-<timestamp>-call-<id>/recording/recording_render_proxy_1080p.mp4
Fact Value
Birth (stat) 2026-09-14 11:52:15 UTC (matches START)
Last modify (stat) 2026-09-14 12:00:53 UTC
Duration (ffprobe) 509.966667 s (~8:30)
Resolution 1920×1200
Frame rate 60 fps
Size 4 169 746 193 bytes (~3.88 GiB)
Codec H.264 High, all-I (keyint=1)
moov atom present in the first 2 MB, not in the last 2 MB (+faststart already applied)
nb_frames 30598

Timeline fits a clean stop: START at 11:52:16 + 510 s ≈ 12:00:46, then a few seconds for the faststart rewrite, mtime 12:00:53. The proxy is a finished encode, not a truncated capture.

SAVE was invoked only after the computer-use turn returned, i.e. after that mtime.

Executor behavior (from this VM’s exec-daemon)

Relevant implementation: LocalRecordScreenExecutor in exec-daemon (record-screen.js / bundled index.js).

Capture

ffmpeg is spawned detached, roughly:

  • -f x11grab -i <display>
  • -draw_mouse 0 (OS cursor is intentionally not burned into the proxy)
  • scale=1920:-2, fps=60
  • libx264 veryfast, CRF 17, keyint=1:min-keyint=1:scenecut=0:bframes=0
  • -movflags +faststart

On child exit, the executor sets:

  • activeRecording.ffmpegDiedEarly = true
  • ffmpegDiedTime, ffmpegExitCode, ffmpegExitSignal

SAVE

Pseudocode of the failing path:

if (!activeRecording) → "No active recording to save"

inputEventLogger.stop()
invokeStoppedCallbackOnce()

if (!childProcess.killed && childProcess.pid)
    stopFfmpegRecording()   // SIGTERM process group, wait for exit
    // on kill() throw → "Failed to send SIGTERM: …"

// only then: access proxy, ffprobe, save recording-data.json, renderPolished()

stopFfmpegRecording:

if (childProcess.killed || !childProcess.pid) return

process.kill(-pid, "SIGTERM")  // or process.kill(pid, "SIGTERM")
// catch → reject Failed to send SIGTERM: <error>

Node ChildProcess.killed is false if the process exited on its own (it is only true if this handle called .kill()). .pid remains set after exit. process.kill(deadPid) → ESRCH.

SAVE catch:

cleanupRecording(state)
invokeStoppedCallbackOnce()
activeRecording = null
return failure

So:

  1. ffmpeg already exited and finalized the proxy (+faststart, moov at start).
  2. SAVE still takes the SIGTERM branch because killed === false and pid is set.
  3. kill ESRCH.
  4. Catch nulls activeRecording even though the proxy is valid.
  5. renderPolished never runs.
  6. Retry cannot save either.

ffmpegDiedEarly is unused on this path. cleanupRecording already treats a dead process as best-effort; SAVE’s stopFfmpegRecording does not.

Why the official cursor is missing

By design the proxy is recorded with -draw_mouse 0. Mouse motion/clicks are supposed to live in recording-data.json (InputEventLogger) and be composited in renderPolished onto recording_demo.mp4.

Because SAVE never got past SIGTERM:

  • no recording-data.json in the staging recording/ directory (only the proxy mp4 was present)
  • no polished demo
  • ffmpeg-transcoded copies of the proxy have at most a tiny OS pointer, if any — not the Cloud Agent demo cursor shown in Cursor docs

CLI even documents that polished rendering is coupled to the default computer-use API width (1280):

--computer-use-api-width … WARNING: do not change this when --record-screen-enabled
— RecordScreen polished rendering only works at the default 1280 API width.

Likely trigger for ffmpeg already being dead

Not confirmed from logs (no recorder stderr persisted in staging). Consistent with:

  • ffmpeg already received a stop and completed +faststart before SAVE
  • timing lined up with computer-use finishing (a separate ~1280×720 encode also appeared under /tmp at ~12:00)

Unrelated to disk full or kernel OOM.

Even if something else stopped ffmpeg, SAVE should still polish a complete proxy.

Suggested fix

On SAVE:

  1. If ffmpegDiedEarly or kill would be ESRCH or the child has already exited, do not fail. Treat ffmpeg as stopped.
  2. If recording_render_proxy_1080p.mp4 exists and ffprobe returns a sane duration, continue: write recording-data.json, renderPolished, move to artifacts.
  3. Only fail if the proxy is missing, unreadable, or duration is 0.
  4. Do not clear activeRecording in the SIGTERM catch until polish has been attempted (or split “stop ffmpeg” errors from “cannot polish” errors).

Optional: if computer-use teardown can SIGTERM the RecordScreen ffmpeg process group, stop doing that, or have teardown call the same “already dead → continue SAVE” path.

Impact

  • Cloud Agent walkthrough videos fall back to the unpolished proxy (or a 1280×720 computer-use encode).
  • Docs-style cursor overlay never appears.
  • Agents that follow the walkthrough skill (START → computer use → SAVE) hit this whenever ffmpeg exits before SAVE, including after a successful capture.

Attachments Cursor can pull from this VM / run

  • Run bc-14f0acfe-baba-4673-b490-399ebaf43431
  • Staging proxy path above (still on disk at report time)
  • Tool results: START_RECORDING success; SAVE_RECORDING ESRCH; second SAVE_RECORDING “No active recording to save”

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!

  1. …
  2. …
  3. …

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 @mubshrx, thanks for the detailed report, and for the write-up of the save path. I’ve flagged this for the team.

In the meantime, you can:

  1. Keep individual recordings short (a few minutes at most) and call SAVE_RECORDING right after the interaction you want to capture, rather than recording one long session. In your run, a later short START → SAVE pair completed normally.

  2. If SAVE_RECORDING does fail this way, the raw capture is still valid at /opt/cursor/recording-staging//recording/recording_render_proxy_1080p.mp4. The agent can copy that file into /opt/cursor/artifacts so it shows up as an artifact (it won’t have the cursor overlay, but the footage should be intact).

Thanks.

Yep, got that workaround.

The second time recording got saved correctly including the decorated cursor.

Glad the second recording came through with the cursor overlay!

The underlying problem, where SAVE_RECORDING failed after the ffmpeg process had already exited and the complete capture never got rendered, has been addressed on our side and will be rolled out soon if it hasn’t been already. Once it’s live, saving after a long recording should produce the polished video instead of failing with that error.

Thanks for addressing the issue. Will watch out for the next update.