Skip to content

fix: normalize saveLocks key in persistence.ts to prevent trailing-slash collisions #11

Description

@randomm

Background

PR #10 introduced a saveLocks: Map<string, Promise<void>> mutex in persistence.ts (used by upsertEntry and removeEntry) to serialize concurrent writes per cwd. The key is the raw cwd string. Two callers passing /repo and /repo/ get distinct mutex entries, defeating serialization for the same physical path.

Flagged during the six-pass adversarial review for #2 and accepted as a future improvement so we could land the worktree plugin.

Fix

Normalize the lock key via path.resolve(cwd) before lookup/insert. resolve('/repo') and resolve('/repo/') both return /repo on POSIX, and standardize Windows separators.

import { resolve as pathResolve } from 'node:path';

// inside upsertEntry/removeEntry:
const key = pathResolve(cwd);
const prev = saveLocks.get(key) ?? Promise.resolve();
// ...
saveLocks.set(key, prev.then(() => next));
// ...
if (saveLocks.get(key) === next) saveLocks.delete(key);

Apply consistently to all saveLocks.get/set/delete call sites.

Acceptance criteria

  • All saveLocks accesses use path.resolve(cwd) as the key
  • Test: two parallel upsertEntry calls using testDir and testDir + '/' (or testDir.slice(0, -1)) both succeed and both entries land in worktrees.json
  • No regression on the existing 169 tests
  • Coverage thresholds preserved (persistence.ts ≥ 85%)

Out of scope

  • Cross-process locking (this is in-process only by design)
  • Symlink resolution beyond path.resolve defaults

Related

PR #10 (worktree plugin foundation), Fixes #2 closed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions