Skip to content

fix(groups): reject rename when target path collides#1556

Open
dolbb wants to merge 4 commits into
asheshgoplani:mainfrom
dolbb:fix/group-rename-collision
Open

fix(groups): reject rename when target path collides#1556
dolbb wants to merge 4 commits into
asheshgoplani:mainfrom
dolbb:fix/group-rename-collision

Conversation

@dolbb

@dolbb dolbb commented Jul 2, 2026

Copy link
Copy Markdown

Summary

  • Renaming a group to a name matching an existing sibling silently overwrote the existing entry in GroupTree.Groups. The orphaned group's sessions dropped out of GetAllInstances(), and the following SaveWithGroups swept them from state.db via the full-table DELETE ... WHERE id NOT IN (...) rewrite — permanent data loss, no error to the user, and no way to retry the rename to recover.
  • RenameGroup now returns a new ErrGroupAlreadyExists for both top-level and subtree collisions (a rename that would rewrite a descendant onto an occupied path is rejected too).
  • TUI (internal/ui/home.go) and web mutator (internal/ui/web_mutator.go) callers surface the error before the save, so the tree stays untouched on collision.

Repro (before this PR)

  1. Create two sibling groups at the root: MySessions (the default one) and inner, with sessions in each.
  2. Rename MySessionsinner.
  3. Result: inner and its sessions are gone from the UI and from state.db. Trying to rename again silently no-ops because the original name no longer resolves.

Related

Test plan

  • Added TestRenameGroup_RejectsCollision — sibling collision returns ErrGroupAlreadyExists; both groups and their sessions remain intact.
  • Added TestRenameGroup_RejectsSubtreeCollision — collision at a descendant path (AlphaBeta when Beta/Child already exists) is rejected before any mutation lands.
  • Existing TestRenameGroup, TestRenameGroupWithSubgroups, TestRenameSubgroup still pass unchanged.
  • TestHomeRenameGroupWithR (TUI hotkey path) still passes.
  • go build ./... clean; go test ./internal/session/... ./internal/web/... green.

Summary by CodeRabbit

  • Bug Fixes
    • Group renames now correctly fail when the target path already exists or would conflict with any subtree path.
    • Renaming a non-existent group now returns a clear failure instead of proceeding.
    • Interface and web updates now stop immediately on rename failure, preventing inconsistent saved state.
  • Tests
    • Added coverage for rename failures: non-existent groups and both direct/subtree collision cases, verifying the original groups and sessions remain unchanged.

Renaming a group to a name matching an existing sibling silently
overwrote the existing entry in GroupTree.Groups. The orphaned group's
sessions were then dropped from GetAllInstances(), and the following
SaveWithGroups swept them from state.db via the full-table
DELETE ... WHERE id NOT IN rewrite — permanent data loss.

RenameGroup now returns ErrGroupAlreadyExists for both top-level and
subtree collisions; TUI and web mutator callers surface the error
before persisting, so the tree stays untouched.

Related: asheshgoplani#1550 — full-table sweep is the amplifier, tracked separately.
@dolbb dolbb requested a review from asheshgoplani as a code owner July 2, 2026 21:30
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

GroupTree.RenameGroup now reports missing sources and rename collisions through errors. UI callers stop failed operations before persistence, and tests verify that rejected renames preserve groups, subtrees, and sessions.

Changes

RenameGroup error handling

Layer / File(s) Summary
RenameGroup validation and error contracts
internal/session/groups.go
RenameGroup returns errors for missing sources and direct or subtree collisions, while preserving no-op and successful rename behavior.
Caller propagation and validation tests
internal/ui/home.go, internal/ui/web_mutator.go, internal/session/groups_test.go
UI handlers surface rename errors and stop processing; tests verify missing-source and collision failures leave existing state unchanged.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
    participant HomeUI
    participant WebMutator
    participant GroupTree

    HomeUI->>GroupTree: RenameGroup(oldPath, newName)
    alt validation fails
        GroupTree-->>HomeUI: ErrGroupNotFound or ErrGroupAlreadyExists
        HomeUI->>HomeUI: setError and stop processing
    else success
        GroupTree-->>HomeUI: nil
        HomeUI->>HomeUI: rebuild and save rename state
    end

    WebMutator->>GroupTree: RenameGroup(groupPath, newName)
    alt validation fails
        GroupTree-->>WebMutator: error
        WebMutator-->>WebMutator: return error before persistence
    else success
        GroupTree-->>WebMutator: nil
        WebMutator->>WebMutator: persist updated state
    end
Loading
🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test_coverage_per_surface ⚠️ Warning Core session tests exist, but TUI/Web only have success or reload-race tests; no direct test proves rename collisions surface an error and skip persistence at the caller level. Add TUI dialog/update and web PATCH tests using real failure paths (not stubs) to assert ErrGroupAlreadyExists/ErrGroupNotFound is shown and no save/mutation occurs.
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits and accurately describes the rename-collision fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Remote_parity ✅ Passed RenameGroup only mutates local groupTree/session rows; an existing UI test explicitly notes RemoteSession coverage is not applicable here.
✨ 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

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/session/groups.go (1)

1142-1147: 🚀 Performance & Scalability | 🔵 Trivial

Silent no-op when oldPath is missing.

Returning nil when the source group doesn't exist means a caller (e.g. if the UI holds a stale group path due to a concurrent modification) gets a "success" signal even though nothing happened. Given this PR's goal is to surface rename failures rather than fail silently, consider returning a distinct sentinel (e.g. ErrGroupNotFound) here too for consistency, though this is a pre-existing behavior and a narrow edge case.

🤖 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 `@internal/session/groups.go` around lines 1142 - 1147, RenameGroup currently
returns nil when the source group path is missing, which makes a failed rename
look successful. Update GroupTree.RenameGroup to return a distinct not-found
error (such as ErrGroupNotFound) from the missing-oldPath branch, and ensure
callers can distinguish this from a successful rename; keep the existing
ErrGroupAlreadyExists behavior for destination collisions.
🤖 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.

Nitpick comments:
In `@internal/session/groups.go`:
- Around line 1142-1147: RenameGroup currently returns nil when the source group
path is missing, which makes a failed rename look successful. Update
GroupTree.RenameGroup to return a distinct not-found error (such as
ErrGroupNotFound) from the missing-oldPath branch, and ensure callers can
distinguish this from a successful rename; keep the existing
ErrGroupAlreadyExists behavior for destination collisions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 285ff1fa-30e5-4e64-bfef-685ec34560fc

📥 Commits

Reviewing files that changed from the base of the PR and between f70f19e and 1a6be09.

📒 Files selected for processing (4)
  • internal/session/groups.go
  • internal/session/groups_test.go
  • internal/ui/home.go
  • internal/ui/web_mutator.go

dolbb and others added 2 commits July 3, 2026 01:29
Address CodeRabbit review on asheshgoplani#1556. Consistent with this PR's
"surface rename failures" goal — the missing-oldPath branch used
to return nil, making a rename of a stale/gone group look
successful to the caller. TUI and web mutator already surface
whatever RenameGroup returns, so they now show a clear error.
…f RenameTargetPath and pendingGroupOps reapply

Committed by Ashesh Goplani

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/ui/web_mutator.go (1)

545-551: 🩺 Stability & Availability | 🔵 Trivial

Non-atomic delete-then-save pattern risks data loss on crash.

DeleteGroupSubtree and SaveWithGroups are separate SQLite operations. A process crash between them leaves the old group rows deleted but the new paths unsaved — the group (and its sessions) silently disappears from state.db on the next reload. This mirrors the existing DeleteGroup pattern, but applying it to RenameGroup widens the blast radius since a rename is a non-destructive user action that shouldn't risk data loss.

Consider wrapping both operations in a single SQLite transaction so the delete and re-add are atomic. As per path instructions, SQLite write atomicity is a key concern for internal/session/** code.

🤖 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 `@internal/ui/web_mutator.go` around lines 545 - 551, Make the RenameGroup flow
encompassing DeleteGroupSubtree and SaveWithGroups execute within one SQLite
transaction, committing only after both operations succeed and rolling back on
any error or crash. Update the relevant storage API or transaction-aware methods
rather than leaving the existing separate calls in RenameGroup, preserving no-op
rename behavior and returning contextual errors.

Source: Path instructions

🤖 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.

Nitpick comments:
In `@internal/ui/web_mutator.go`:
- Around line 545-551: Make the RenameGroup flow encompassing DeleteGroupSubtree
and SaveWithGroups execute within one SQLite transaction, committing only after
both operations succeed and rolling back on any error or crash. Update the
relevant storage API or transaction-aware methods rather than leaving the
existing separate calls in RenameGroup, preserving no-op rename behavior and
returning contextual errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bdfc387e-4fb2-4818-aa96-6712dae94143

📥 Commits

Reviewing files that changed from the base of the PR and between 1a6be09 and c2f6cfd.

📒 Files selected for processing (4)
  • internal/session/groups.go
  • internal/session/groups_test.go
  • internal/ui/home.go
  • internal/ui/web_mutator.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/ui/home.go

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.

2 participants