Skip to content

Bug: --lock PORT locks wrong directory's allocation instead of reassigning #77

Description

@dapi

Summary

Improve --lock PORT behavior and fix bugs with locked ports handling.

Full Decision Matrix: docs/decision-matrix.md


Terminology (from Decision Matrix)

Term Definition
busy Port fails TCP bind: net.Listen("tcp", ":PORT") returns error
free Port successfully binds via net.Listen()
locked Allocation has locked: true in allocations.yaml
current dir Result of os.Getwd() after filepath.Clean()
name Allocation name from --name flag (default: main)

Part 1: --lock PORT Command Improvements

1.1 Show directory in success message

Current:

Locked port 3001 for 'main'

Proposed:

Locked port 3001 for 'main' in ~/worktrees/feature-4191

1.2 Smart --force requirement

Require --force if:

  • Port is locked for another directory (regardless of busy/free)

Block completely (even with --force) if:

  • Port is busy on another directory — cannot reassign running service
  • Error: port 3001 is in use by /path/to/other/dir; stop the service first

Allow without --force if:

  • Port is not allocated at all (first-time lock)
  • Port is allocated to another directory but is free and unlocked (abandoned)

Special case — port busy but NOT in allocations:

  • Without --force: error port 3001 is in use
  • With --force: create allocation (port remains busy, user takes responsibility)

1.3 Unlock old locked allocation on new lock

When locking a new port for directory+name that already has a locked port:

  • Set locked: false on the old port (keep allocation, just unlock)
  • Only affects port with same name

Invariant: At most one locked port per directory+name


Part 2: Bug Fix — Locked Port Ignored When Busy

Problem

When directory has a locked port that is busy, port-selector creates a new allocation instead of returning the locked port.

# Directory has locked port 3001, user's service running on it
$ port-selector --list | grep feature-4191
3001  ~/worktrees/feature-4191  busy  yes  main

# Request port — creates NEW instead of returning locked!
$ port-selector
3056  # Wrong! Should return 3001

Root Cause

  1. FindByDirectoryAndName() returns most recent port by LastUsedAt, ignoring locked status
  2. If returned port is busy, code creates new allocation
  3. Locked ports preserved but duplicates accumulate

Solution

Change priority in FindByDirectoryAndName():

  1. Locked + free → return
  2. Locked + busy → return (user's service already running)
  3. Unlocked + free → return
  4. Unlocked + busy → skip, find another or create new

Key insight: Busy locked port means user's service is already running on their reserved port. Return it — that's exactly what they want.


Part 3: Bug Fix — Multiple Locked Ports for Same Directory+Name

Problem

Directory can have multiple locked ports for same name:

$ port-selector --list | grep feature-4191
3001  ~/worktrees/feature-4191  free  yes  main
3004  ~/worktrees/feature-4191  free  yes  main  # Two locked!

Root Cause

--lock 3001 --force doesn't unlock old locked port (3004).

Solution

Covered in Part 1.3: unlock old locked port when locking new one for same directory+name.


Implementation Checklist

  • 1.1: Show directory in lock success message
  • 1.2: Implement smart --force logic per Decision Matrix
  • 1.3: Unlock old locked port on new lock
  • 2: Fix FindByDirectoryAndName() to prioritize locked ports
  • 3: Add invariant check/cleanup for multiple locked ports
  • Tests for all scenarios in Decision Matrix
  • Update --help text for --force behavior

Environment

  • port-selector v0.9.5
  • Linux

Spec v2.0 | Decision Matrix: docs/decision-matrix.md | 2026-02-02

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions