Detect file renames without triggering deletions#587
Detect file renames without triggering deletions#587huntercaron wants to merge 9 commits intomainfrom
Conversation
When watcher sanitizes file/folder names (e.g., "race?.tsx" → "race_.tsx"), the fs.rename() call triggers echo events from chokidar that were being processed as real user events. This caused: - False file uploads (echo add events) - False file deletions (echo unlink events undoing the sanitization) - Interference with rename detection Solution: - Track recently sanitized paths in recentSanitizations set - Record both unsanitized and sanitized paths after fs.rename() - Suppress events at top of emitEvent if they match recent sanitizations - Add same-path rename guard to dispatchEvent for defense-in-depth - Add tests for echo suppression scenarios This complements the existing rename detection feature which uses the Framer SDK's codeFile.rename() API to properly handle file renames without deletion operations.
Move the file-synced WebSocket message inside the existing-file guard so the CLI is not told a rename succeeded when the target file was never found in Framer. Also reorder rename-effect tracking to update state only after a successful send, and fix minor type/style issues.
Reuse hashFileContent from state-persistence instead of maintaining a private copy.
There was a problem hiding this comment.
Pull request overview
This PR adds end-to-end rename support to the code-link workflow (CLI watcher → CLI controller → plugin) by detecting unlink+add rename patterns locally, sending a new file-rename message type, and applying the rename remotely via Framer’s codeFile.rename() API. It also adds suppression for chokidar “echo” events caused by the watcher’s own sanitization renames to avoid false remote deletions.
Changes:
- Add hash-based unlink+add pairing with a short buffer to emit a single local
renamewatcher event. - Introduce
file-renameCLI→plugin message + controller effect to send renames and update local tracking. - Implement plugin-side remote rename application via
codeFile.rename()and update shared message types accordingly.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| plugins/code-link/src/api.ts | Adds applyRemoteRename() to rename Framer code files and acknowledge via file-synced. |
| plugins/code-link/src/App.tsx | Handles incoming file-rename messages and invokes the new API method. |
| packages/code-link-shared/src/types.ts | Extends shared message union/validation to include file-rename. |
| packages/code-link-cli/src/utils/project.ts | Adjusts directory-name sanitization rules (now preserves spaces). |
| packages/code-link-cli/src/utils/hash-tracker.ts | Reuses shared hashing utility from state persistence (removes local crypto hashing). |
| packages/code-link-cli/src/types.ts | Adds rename watcher event kind and oldRelativePath metadata. |
| packages/code-link-cli/src/helpers/watcher.ts | Implements rename detection + sanitization echo suppression + buffer cleanup on close. |
| packages/code-link-cli/src/helpers/watcher.test.ts | Adds unit tests covering rename detection, buffering, and echo suppression. |
| packages/code-link-cli/src/helpers/installer.ts | Minor formatting cleanup (whitespace). |
| packages/code-link-cli/src/helpers/files.test.ts | Adjusts test typings for conflict content values. |
| packages/code-link-cli/src/controller.ts | Adds rename handling in the state machine and a new SEND_FILE_RENAME effect. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Dang ok I guess I will start doing copilot + devin + cursor haha |
…on on send failure
|
@cursor review |
|
@devin are you here to review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
| if (!framer.isAllowedTo("CodeFile.rename")) { | ||
| log.warn("No permission to rename code files") | ||
| return false | ||
| } |
There was a problem hiding this comment.
No error sent when rename permission denied
Medium Severity
When framer.isAllowedTo("CodeFile.rename") returns false, applyRemoteRename returns false without sending any socket message back to the CLI. Every other failure path in this method sends an error message via the socket (lines 173–179, 189–195, 221–227). Without a response, the CLI's pendingRenames entry (set in SEND_FILE_RENAME) is never cleaned up. A future UPDATE_FILE_METADATA event for the same fileName would incorrectly trigger old-file cleanup from the stale entry, corrupting hashTracker and fileMetadataCache state.
Additional Locations (1)
| } | ||
|
|
||
| return [] | ||
| } |
There was a problem hiding this comment.
Rename handler lacks echo prevention checks
Medium Severity
The new SEND_FILE_RENAME effect handler sends renames to the plugin without any echo prevention via hashTracker. The existing SEND_LOCAL_CHANGE checks hashTracker.shouldSkip and LOCAL_INITIATED_FILE_DELETE checks hashTracker.shouldSkipDelete, but SEND_FILE_RENAME checks neither. When remote-initiated writes (new file + deleted old file) happen close together, the watcher's rename detection can combine them into a rename event that bypasses the per-event echo guards, causing a spurious rename message to be sent back to the plugin.


Description
This PR implements proper file rename detection in the code-link CLI and fixes the echo event bug that was causing false deletions. The changes include:
codeFile.rename()API to properly rename files without deletionrace?.tsx→race_.tsxorNew Folder With Items/→New_Folder_With_Items/)Without the echo suppression fix, renaming files locally would trigger a delete operation on the remote plugin, undoing the rename. The 100ms buffer for rename detection ensures that both unlink and add events are matched regardless of their arrival order.
Changelog
file-renamemessage type for CLI-to-plugin communicationSEND_FILE_RENAMEeffect in CLI controller to handle rename eventsapplyRemoteRename()method in plugin API using Framer'scodeFile.rename()Testing