Skip to content

fix: prevent EPERM on delete/organize when video is still playing#92

Merged
martig7 merged 3 commits into
mainfrom
claude/recursing-hawking-5b36e8
Apr 17, 2026
Merged

fix: prevent EPERM on delete/organize when video is still playing#92
martig7 merged 3 commits into
mainfrom
claude/recursing-hawking-5b36e8

Conversation

@martig7
Copy link
Copy Markdown
Owner

@martig7 martig7 commented Apr 17, 2026

Summary

  • VideoPlayer now pauses and removes the <video> src (closing the browser's HTTP range-request stream) before calling onDelete or starting organize — this releases the fs.createReadStream handle on the main process side before any filesystem operation is attempted
  • deleteFile() in recordingService.js is now async and wraps the unlink with waitForUnlock (4 × 500ms) + unlinkWithRetry (4 × 500ms), catching any residual gap between the browser aborting its stream and the OS releasing the handle
  • Auto-delete paths in fileManager.js (deleteFullRecording after organize, two sites) replaced bare fs.unlinkSync with the existing unlinkWithRetry helper, consistent with every other deletion in that file

Root cause

On Windows, fs.createReadStream holds an OS file handle for each range request the browser's <video> element makes while buffering. fs.unlinkSync and fs.renameSync fail with EPERM while those handles are open. The failure was intermittent because it depended on whether a range request happened to be in-flight at the exact moment the user triggered delete or organize.

Test plan

  • Play a recording/clip, then delete it while it's playing — should succeed without permission error
  • Play a recording, open the organize panel, and confirm organize while it's playing — should move the file without error
  • Delete a recording/clip while it's paused — should still work as before
  • Verify that cancelling the delete modal after clicking Delete still reloads the video normally

🤖 Generated with Claude Code

martig7 and others added 3 commits April 17, 2026 16:42
…ld by the player

On Windows, open fs.createReadStream handles (from the video player's HTTP
range requests) block fs.unlinkSync and fs.renameSync with EPERM.

- VideoPlayer now pauses and removes the video src (triggering stream abort
  and handle release) before calling onDelete or starting organize
- deleteFile() is async and uses waitForUnlock + unlinkWithRetry so any
  residual timing gap between the browser abort and OS handle release is
  handled with retries instead of a hard failure
- Auto-delete paths in fileManager.js (deleteFullRecording after organize)
  replaced bare fs.unlinkSync with the existing unlinkWithRetry helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
deleteFile() is now async so all call sites must await it:
- apiServer.js: 3 places (clips/delete, recordings/delete, delete-batch)
- recordingService.test.js: 6 tests updated to async/await

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
runAutoDelete called deleteFile() without await, getting a Promise
instead of the result object. Made runAutoDelete async, updated
fire-and-forget callers in main.js and watcherHandlers.js to use
.catch(), and converted all runAutoDelete unit tests to async/await.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@martig7 martig7 merged commit 3e1595b into main Apr 17, 2026
2 checks passed
@martig7 martig7 deleted the claude/recursing-hawking-5b36e8 branch April 17, 2026 21:58
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.

1 participant