From fa79637b69a03307f21ec860d22887c726bbda78 Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Mon, 2 Mar 2026 10:31:29 +0000 Subject: [PATCH 1/3] fix: show allocation directory in --list instead of Docker container Cwd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, --list replaced the allocation's saved directory with the Docker container's working directory. This was confusing when Docker from another project hijacked an allocated port — the user saw a foreign directory and couldn't tell the port was actually theirs. Also adds a warning to stderr when returning a busy port for an existing allocation, so the user knows the port is occupied and can use --forget to get a new one. Co-Authored-By: Claude Opus 4.6 --- cmd/port-selector/main.go | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/cmd/port-selector/main.go b/cmd/port-selector/main.go index da756e0..73dd6b8 100644 --- a/cmd/port-selector/main.go +++ b/cmd/port-selector/main.go @@ -379,6 +379,17 @@ func runWithName(name string) error { // ALWAYS return the same port for (directory, name) - port is stable per directory if existing := store.FindByDirectoryAndName(cwd, name); existing != nil { debug.Printf("main", "found existing allocation for name %s: port %d (locked=%v)", name, existing.Port, existing.Locked) + + // Warn if the port is busy (occupied by another process) + if !port.IsPortFree(existing.Port) { + procInfo := port.GetPortProcess(existing.Port) + if procInfo != nil && procInfo.Name != "" { + fmt.Fprintf(os.Stderr, "warning: port %d is busy (%s); use --forget to get a new port\n", existing.Port, procInfo.Name) + } else { + fmt.Fprintf(os.Stderr, "warning: port %d is busy; use --forget to get a new port\n", existing.Port) + } + } + // Update last_used timestamp for the specific port being issued if !store.UpdateLastUsedByPort(existing.Port) { debug.Printf("main", "warning: UpdateLastUsedByPort failed for port %d", existing.Port) @@ -798,18 +809,7 @@ func runList() error { maxDirLen := 0 for i, alloc := range allAllocs { - directory := alloc.Directory - - // Check if port is busy and has Docker info - if !port.IsPortFree(alloc.Port) { - if procInfo := port.GetPortProcess(alloc.Port); procInfo != nil { - if procInfo.ContainerID != "" && procInfo.Cwd != "" && procInfo.Cwd != "/" { - directory = procInfo.Cwd - } - } - } - - shortDir := pathutil.ShortenHomePath(directory) + shortDir := pathutil.ShortenHomePath(alloc.Directory) allDirectories[i] = shortDir if len(shortDir) > maxDirLen { @@ -828,7 +828,6 @@ func runList() error { username := "-" pid := "-" process := "-" - directory := alloc.Directory // Determine SOURCE and use saved external info for external allocations source := "free" @@ -874,20 +873,12 @@ func runList() error { } else if procInfo.ContainerID != "" { // Docker container detected via fallback process = "docker-proxy" - if procInfo.Cwd != "" && procInfo.Cwd != "/" { - directory = procInfo.Cwd - } } else { // Have user but no PID and no Docker - mark incomplete only if no saved name if alloc.ProcessName == "" { hasIncompleteInfo = true } } - - // Use live Docker directory if available and better than saved - if procInfo.ContainerID != "" && procInfo.Cwd != "" && procInfo.Cwd != "/" { - directory = procInfo.Cwd - } } } @@ -903,10 +894,6 @@ func runList() error { // Get pre-calculated directory string and truncate if needed shortDir := allDirectories[i] - // If directory was updated by Docker check, re-shorten it - if directory != alloc.Directory { - shortDir = pathutil.ShortenHomePath(directory) - } // Cap at 40 characters maximum if len(shortDir) > maxDirWidth { shortDir = truncateDirectoryPath(shortDir, maxDirWidth) From 4c13a01c3064bb805589a8cfdc3c5e0973ac235b Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Mon, 2 Mar 2026 10:48:20 +0000 Subject: [PATCH 2/3] test: add warning assertion for busy port in stderr Extends TestPortSelector_ReturnsSamePortEvenWhenBusy to verify that when a port is busy, stderr contains "warning: port ... is busy" with a --forget hint for the user. Co-Authored-By: Claude Opus 4.6 --- cmd/port-selector/main_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/port-selector/main_test.go b/cmd/port-selector/main_test.go index 6e23dc4..c46f242 100644 --- a/cmd/port-selector/main_test.go +++ b/cmd/port-selector/main_test.go @@ -1254,6 +1254,15 @@ func TestPortSelector_ReturnsSamePortEvenWhenBusy(t *testing.T) { t.Errorf("BUG REPRODUCED: expected same port %s, got different port %s", initialPort, secondPort) t.Errorf("Port should be stable for the same directory, even when busy") } + + // Step 5: Verify warning is printed to stderr when port is busy + stderrStr := stderr2.String() + if !strings.Contains(stderrStr, "warning: port") || !strings.Contains(stderrStr, "is busy") { + t.Errorf("expected 'warning: port ... is busy' in stderr, got: %q", stderrStr) + } + if !strings.Contains(stderrStr, "--forget") { + t.Errorf("expected '--forget' hint in stderr warning, got: %q", stderrStr) + } } func TestPortSelector_PortStabilityAcrossMultipleCalls(t *testing.T) { From abce2ac808e9224c0240184b28e86d8ee6d03449 Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Mon, 2 Mar 2026 10:54:21 +0000 Subject: [PATCH 3/3] fix: resolve CI failures (formatting + unused import) - Remove extra blank lines in allocations_test.go (gofmt) - Remove unused fmt.Sscanf call and fmt import in main_test.go (errcheck) Co-Authored-By: Claude Opus 4.6 --- cmd/port-selector/main_test.go | 3 --- internal/allocations/allocations_test.go | 7 ------- 2 files changed, 10 deletions(-) diff --git a/cmd/port-selector/main_test.go b/cmd/port-selector/main_test.go index c46f242..fe4fb49 100644 --- a/cmd/port-selector/main_test.go +++ b/cmd/port-selector/main_test.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "fmt" "net" "os" "os/exec" @@ -1227,8 +1226,6 @@ func TestPortSelector_ReturnsSamePortEvenWhenBusy(t *testing.T) { t.Logf("Initial port: %s", initialPort) // Step 2: Simulate user's service running on that port - portNum := 0 - fmt.Sscanf(initialPort, "%d", &portNum) ln, err := net.Listen("tcp", ":"+initialPort) if err != nil { t.Skipf("could not occupy port %s for test: %v", initialPort, err) diff --git a/internal/allocations/allocations_test.go b/internal/allocations/allocations_test.go index ab4a485..96f0f86 100644 --- a/internal/allocations/allocations_test.go +++ b/internal/allocations/allocations_test.go @@ -2543,10 +2543,6 @@ func TestSetAllocation_PreservesLockedPorts(t *testing.T) { // Tests for issue #77: FindByDirectoryAndNameWithPriority - - - - // Tests for UnlockOtherLockedPorts func TestUnlockOtherLockedPorts(t *testing.T) { @@ -2630,9 +2626,6 @@ func TestUnlockOtherLockedPorts_EmptyStore(t *testing.T) { } } - - - func TestRefreshExternalAllocations_KeepsActive(t *testing.T) { store := NewStore() now := time.Now().UTC()