Skip to content

WIP: feat(naming-collisions)/handle naming collision across clone, create, rename and drag & drop#8423

Draft
sachin-thakur-bruno wants to merge 11 commits into
usebruno:mainfrom
sachin-thakur-bruno:feat/naming-collisions-app
Draft

WIP: feat(naming-collisions)/handle naming collision across clone, create, rename and drag & drop#8423
sachin-thakur-bruno wants to merge 11 commits into
usebruno:mainfrom
sachin-thakur-bruno:feat/naming-collisions-app

Conversation

@sachin-thakur-bruno

@sachin-thakur-bruno sachin-thakur-bruno commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Description

https://usebruno.atlassian.net/browse/BRU-894

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • New Features

    • Cloning, creating, moving, and saving items now resolve name conflicts automatically, reducing errors from duplicate names.
    • Cloned items and folders now use clearer copy naming and open the actual created location when needed.
  • Bug Fixes

    • Improved move behavior to keep item paths consistent after conflict resolution.
    • Fixed several cases where file or folder creation could fail unnecessarily when a name was already in use.
  • Tests

    • Expanded coverage for naming rules and collision-safe file operations.
@sachin-thakur-bruno sachin-thakur-bruno changed the title feat(naming-collisions)/handle naming collision across clone, create, rename and drag & drop Jun 30, 2026
@sachin-thakur-bruno sachin-thakur-bruno marked this pull request as draft June 30, 2026 07:29
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d1cef97b-26ac-4898-9c14-ec1a22150f0c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Introduces shared filesystem-safe naming utilities (sanitizeName, validateName, validateNameError, nextSuffixedName) in bruno-common, consolidates their implementations across the app and Electron packages, adds atomic collision-resolving primitives (writeFileUnique, mkdirUnique, withDirLock) to Electron's filesystem layer, removes all renderer-side duplicate-name pre-checks in favor of Electron-resolved pathnames, and wires one-click clone in the sidebar CollectionItem.

Changes

Collision-safe naming overhaul & one-click clone

Layer / File(s) Summary
Shared naming utilities
packages/bruno-common/src/utils/naming.ts, packages/bruno-common/src/utils/naming.spec.ts, packages/bruno-common/src/utils/index.ts
Introduces and tests sanitizeName, validateName, validateNameError, and nextSuffixedName; re-exports all four from the common utils index.
App regex.js delegates to bruno-common
packages/bruno-app/src/utils/common/regex.js
Removes ~54 lines of local name-validation logic; re-exports the three name helpers from @usebruno/common/utils.
Electron filesystem collision-safe primitives
packages/bruno-electron/src/utils/filesystem.js, packages/bruno-electron/src/utils/tests/filesystem/index.spec.js
Adds writeFileUnique, mkdirUnique, getUniqueRenamePath, getUniqueTargetPath, copyPathTo, and withDirLock; removes copyPath; delegates sanitize/validate to @usebruno/common; updates tests.
Electron IPC handlers use collision-safe primitives
packages/bruno-electron/src/ipc/collection.js
Updates create-collection, clone-collection, new-request, save-transient-request, rename-item-filename, new-folder, clone-folder, move-item, move-item-cross-format, and save-scratch-request to use the new atomic helpers and return resolved pathnames to the renderer.
Renderer actions remove duplicate-name pre-checks
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
Removes generateUniqueName and all renderer-side duplicate rejection; introduces copyDisplayName; uses result?.pathname from IPC results when queuing OPEN_REQUEST for all request/folder creation and paste flows; reconciles newPathname in move handler.
One-click clone in CollectionItem
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
Adds handleCloneItem that dispatches cloneItem with "<name> copy" display/filesystem names and shows toast feedback; wires keyboard shortcut and context menu to it.

Sequence Diagram(s)

sequenceDiagram
  participant UI as CollectionItem / Redux Action
  participant IPC as Electron IPC Handler
  participant FS as filesystem.js (writeFileUnique / mkdirUnique)

  UI->>IPC: renderer:new-request / clone-folder / move-item (name, path)
  IPC->>FS: writeFileUnique / mkdirUnique / getUniqueTargetPath
  FS-->>IPC: resolved pathname (numeric suffix if collision)
  IPC-->>UI: { pathname, filename } / { newPathname }
  UI->>UI: enqueue OPEN_REQUEST with result?.pathname
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • usebruno/bruno#6097: Introduced copy/paste for folders in the same collections/actions.js area, which this PR directly refactors by replacing generateUniqueName with Electron-resolved collision handling.
  • usebruno/bruno#6322: Overlaps in ipc/collection.js clone-collection flow, which this PR also reworks to use mkdirUnique and return resolved pathnames.

Suggested reviewers

  • helloanoop
  • bijin-bruno
  • lohit-bruno

Poem

A clone walks in, no modal in sight,
"copy" appended, the suffix just right.
Electron resolves what collides on disk,
No renderer pre-check, no duplicate risk.
One click, one toast — filesystem bliss! 🗂️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the core change set: handling naming collisions across clone, create, rename, and drag-and-drop flows.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/bruno-electron/src/ipc/collection.js (1)

581-601: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Validate targetFilename before passing it to writeFileUnique().

targetDirname is validated, but targetFilename is renderer-controlled and becomes the base path segment. A value containing path separators can escape the target directory before writeFileUnique() writes the file.

Suggested guard
 const filename = targetFilename || path.basename(sourcePathname);
+if (path.basename(filename) !== filename || path.win32.basename(filename) !== filename) {
+  throw new Error(`${filename} is not a valid filename`);
+}
 const filenameWithoutExt = filename.replace(/\.(bru|yml)$/, '');
+if (!validateName(filenameWithoutExt)) {
+  throw new Error(`${filename} is not a valid filename`);
+}

As per path instructions, path handling must account for platform differences.

Also applies to: 2274-2279

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-electron/src/ipc/collection.js` around lines 581 - 601,
Validate the renderer-controlled targetFilename before it is used to derive the
base name for writeFileUnique() in the collection save flow. Add a path-safety
check in the function that computes filename/filenameWithoutExt so
targetFilename cannot contain separators or any value that would escape
targetDirname, and make the validation platform-aware by using path
parsing/normalization rather than assuming one separator style. Keep the guard
close to the existing filename resolution logic so the same protection applies
wherever writeFileUnique() is called in this code path, including the other
referenced save locations.

Source: Path instructions

🧹 Nitpick comments (2)
packages/bruno-electron/src/utils/tests/filesystem/index.spec.js (1)

64-74: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add behavioral coverage for the atomic create helpers.

This only asserts the computed target name. Please add tests for writeFileUnique() and mkdirUnique() creating name, then name1 under collision, ideally with parallel Promise.all() calls to exercise the EEXIST retry path. As per coding guidelines, “Add tests for any new functionality or meaningful changes.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-electron/src/utils/tests/filesystem/index.spec.js` around
lines 64 - 74, The current filesystem spec only covers getUniqueTargetPath, but
it misses behavioral coverage for the atomic create helpers. Update the tests in
index.spec.js to exercise writeFileUnique() and mkdirUnique() directly: verify
they create the original name on first call and name1 when the target already
exists, ideally using parallel Promise.all() calls to trigger the EEXIST retry
path. Use the unique symbols getUniqueTargetPath, writeFileUnique, and
mkdirUnique to locate the relevant test block and add the new assertions there.

Source: Coding guidelines

packages/bruno-electron/src/utils/filesystem.js (1)

366-372: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Normalize and clean up directory lock keys.

Use a resolved key and delete completed entries; otherwise equivalent directory spellings can bypass serialization, and dirLockChains grows for every unique directory touched.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-electron/src/utils/filesystem.js` around lines 366 - 372, The
withDirLock helper currently uses the raw dirname as the dirLockChains key and
never removes finished entries, so equivalent path spellings can bypass
serialization and the map can grow unbounded. Update withDirLock to
normalize/resolve the directory before looking up or setting the lock chain, and
ensure the dirLockChains entry is deleted once the operation settles while still
preserving the existing sequential execution behavior around fn().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js`:
- Around line 603-608: The one-click clone handler in CollectionItem currently
allows every sidebar item to call cloneItem, but that action only supports
folders and requests, so standalone app items can take the wrong path. Update
handleCloneItem to first gate cloning with isItemARequest(item) (or add explicit
app cloning support before dispatching cloneItem), and keep the success/error
toast behavior only for the supported item types.
- Around line 603-608: Add a behavioral test for handleCloneItem in
CollectionItem to cover the one-click clone flow for both folder and request
items, asserting the success toast on a resolved cloneItem dispatch and the
error toast on a rejected dispatch. Use the existing CollectionItem clone action
logic to verify the sidebar behavior, but do not duplicate the filename-suffix
resolution cases already covered in the filesystem helper tests.

In `@packages/bruno-common/src/utils/naming.spec.ts`:
- Around line 39-42: The current Windows reserved-name coverage in
naming.spec.ts only checks bare device names, so extend the existing
validateName and validateNameError tests to cover the real cross-platform edge
cases: reject reserved names with extensions like CON.txt, and confirm COM0/LPT0
remain valid. Update the assertions around validateName and validateNameError so
the reserved-name handling is locked down for both plain and dotted inputs
without introducing OS-specific behavior.

In `@packages/bruno-common/src/utils/naming.ts`:
- Line 12: The Windows reserved-name check in reservedDeviceNames is too narrow
and also rejects invalid COM0/LPT0 cases, so update the matcher in naming.ts to
treat device names as reserved even when followed by a file extension (for
example CON.txt or NUL.bru) while only reserving COM1-9 and LPT1-9. Make the
same adjustment wherever this helper is used in the shared naming validation
path so create/rename behavior stays OS-agnostic across the cross-platform app.

In `@packages/bruno-electron/src/ipc/collection.js`:
- Around line 1598-1655: The IPC move handlers currently trust renderer-provided
paths and only check existence, which leaves copy/delete/write operations
exposed to out-of-scope paths. In ipcMain.handle('renderer:move-item') and
ipcMain.handle('renderer:move-item-cross-format'), add collection-boundary
validation for both sourcePathname and targetDirname before calling
copyPathTo(), writeFileUnique(), or removePath(), and reject if either path is
outside the allowed collection. Keep the checks alongside the existing
fs.existsSync and same-folder logic so the move flow only proceeds after both
paths are verified.
- Line 1044: The rename flow is using the IPC-provided newPath before validating
the requested filename, so adjust the main-process rename handler to derive the
destination path from oldPath plus a validated newFilename first, then pass that
computed path into getUniqueRenamePath(). Keep the validation and path
construction together in the same rename logic so untrusted IPC input is not
used for collision resolution until after it has been sanitized.

In `@packages/bruno-electron/src/utils/filesystem.js`:
- Around line 123-128: The retry loop in
`getSafePathToWrite()`/`nextSuffixedName()` can truncate away the numeric
suffix, causing every `EEXIST` retry to map to the same path. Update the
filename generation flow so `getSafeUniqueFilename()` reserves space for both
the suffix and extension before truncation, and have the write loop use that
safe unique name when building `pathname`.
- Around line 347-356: The copy flow in copyPathTo/getUniqueTargetPath is still
race-prone and can overwrite or recursively copy into itself. Update the target
selection and copy checks to claim the destination exclusively before writing,
and add an OS-agnostic guard using path.resolve()/path.relative() to reject any
directory copy where targetPath resolves inside source. Also ensure
mkdir/copyFile paths fail instead of merging existing directories or overwriting
files after the uniqueness check.

---

Outside diff comments:
In `@packages/bruno-electron/src/ipc/collection.js`:
- Around line 581-601: Validate the renderer-controlled targetFilename before it
is used to derive the base name for writeFileUnique() in the collection save
flow. Add a path-safety check in the function that computes
filename/filenameWithoutExt so targetFilename cannot contain separators or any
value that would escape targetDirname, and make the validation platform-aware by
using path parsing/normalization rather than assuming one separator style. Keep
the guard close to the existing filename resolution logic so the same protection
applies wherever writeFileUnique() is called in this code path, including the
other referenced save locations.

---

Nitpick comments:
In `@packages/bruno-electron/src/utils/filesystem.js`:
- Around line 366-372: The withDirLock helper currently uses the raw dirname as
the dirLockChains key and never removes finished entries, so equivalent path
spellings can bypass serialization and the map can grow unbounded. Update
withDirLock to normalize/resolve the directory before looking up or setting the
lock chain, and ensure the dirLockChains entry is deleted once the operation
settles while still preserving the existing sequential execution behavior around
fn().

In `@packages/bruno-electron/src/utils/tests/filesystem/index.spec.js`:
- Around line 64-74: The current filesystem spec only covers
getUniqueTargetPath, but it misses behavioral coverage for the atomic create
helpers. Update the tests in index.spec.js to exercise writeFileUnique() and
mkdirUnique() directly: verify they create the original name on first call and
name1 when the target already exists, ideally using parallel Promise.all() calls
to trigger the EEXIST retry path. Use the unique symbols getUniqueTargetPath,
writeFileUnique, and mkdirUnique to locate the relevant test block and add the
new assertions there.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 605dc777-69b4-479b-8b40-cb304403441b

📥 Commits

Reviewing files that changed from the base of the PR and between 49b9af1 and dd24ca3.

📒 Files selected for processing (9)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-app/src/utils/common/regex.js
  • packages/bruno-common/src/utils/index.ts
  • packages/bruno-common/src/utils/naming.spec.ts
  • packages/bruno-common/src/utils/naming.ts
  • packages/bruno-electron/src/ipc/collection.js
  • packages/bruno-electron/src/utils/filesystem.js
  • packages/bruno-electron/src/utils/tests/filesystem/index.spec.js
Comment on lines +39 to +42
it('rejects Windows reserved device names', () => {
expect(validateName('CON')).toBe(false);
expect(validateName('LPT1')).toBe(false);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the Windows reserved-name edge cases here.

This only covers bare CON/LPT1, so it would miss the real cross-platform regression: CON.txt should be rejected, while COM0/LPT0 should stay valid. Please lock those cases down in both validateName and validateNameError.

As per coding guidelines, Add tests for any new functionality or meaningful changes. As per path instructions, Bruno is a cross-platform Electron desktop app that runs on macOS, Windows, and Linux. Ensure that all code is OS-agnostic.

Also applies to: 59-64

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-common/src/utils/naming.spec.ts` around lines 39 - 42, The
current Windows reserved-name coverage in naming.spec.ts only checks bare device
names, so extend the existing validateName and validateNameError tests to cover
the real cross-platform edge cases: reject reserved names with extensions like
CON.txt, and confirm COM0/LPT0 remain valid. Update the assertions around
validateName and validateNameError so the reserved-name handling is locked down
for both plain and dotted inputs without introducing OS-specific behavior.

Sources: Coding guidelines, Path instructions

Comment thread packages/bruno-common/src/utils/naming.ts
if (!safeToRename(oldPath, newPath)) {
throw new Error(`path: ${newPath} already exists`);
}
newPath = getUniqueRenamePath(oldPath, newPath);

@coderabbitai coderabbitai Bot Jun 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Validate and derive newPath before collision resolution.

newPath is accepted from IPC and passed into getUniqueRenamePath() before newFilename validation. Derive the destination from oldPath + a validated filename in the main process, then resolve collisions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-electron/src/ipc/collection.js` at line 1044, The rename flow
is using the IPC-provided newPath before validating the requested filename, so
adjust the main-process rename handler to derive the destination path from
oldPath plus a validated newFilename first, then pass that computed path into
getUniqueRenamePath(). Keep the validation and path construction together in the
same rename logic so untrusted IPC input is not used for collision resolution
until after it has been sanitized.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before our work the handler also trusted newPath (it did safeToRename(oldPath, newPath) then threw). We replaced the throw with getUniqueRenamePath(...). So now, instead of rejecting a collision, we resolve and rename to a path derived from the renderer's newPath.
Adding a validation for this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sachin-thakur-bruno Agreed — good clarification. This wasn’t introduced by your change; the handler already trusted the IPC-provided newPath before, and this PR just changed the collision behavior from reject to suffix.

Adding validation/deriving the destination in the main process is still the right hardening step here, so that getUniqueRenamePath() only runs on a validated path built from oldPath + newFilename. That would address my concern.


🧠 Learnings used
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Learnt from: sanish-bruno
Repo: usebruno/bruno PR: 8387
File: packages/bruno-cli/src/utils/persist-variables.js:330-337
Timestamp: 2026-06-26T21:24:02.726Z
Learning: In the usebruno/bruno repository, the codebase is standardized to LF line endings. During code review, do not flag CRLF-preservation/line-ending change concerns for these text-based source/config files unless the file format or an explicit workflow/tooling requirement requires preserving CRLF (e.g., a Windows-specific file type or a mandated EOL rule).
Comment thread packages/bruno-electron/src/ipc/collection.js
Comment thread packages/bruno-electron/src/utils/filesystem.js Outdated
Comment thread packages/bruno-electron/src/utils/filesystem.js Outdated
@helloanoop

Copy link
Copy Markdown
Contributor

@sachin-thakur-bruno Would love to see a good bunch of e2e tests for this given that this is a bug prone path.

@sachin-thakur-bruno

Copy link
Copy Markdown
Contributor Author

@sachin-thakur-bruno Would love to see a good bunch of e2e tests for this given that this is a bug prone path.

Yes I will be adding e2e tests for all scenarios.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2 participants