Skip to content

feat: hijacked status in --list + --doctor diagnostic command #82

@dapi

Description

@dapi

Problem

When Docker from another project hijacks an allocated port, --list shows busy with no indication that the process is from a foreign directory. Users can't distinguish "my process is running" from "someone else stole my port" (#81 fixes the directory display, but doesn't add conflict detection).

Additionally, there's no diagnostic tool to find and fix various allocation health issues.

Feature 1: hijacked status in --list

When a port is busy and the process runs from a different directory than the allocation's, show STATUS=hijacked instead of busy, plus footnotes:

PORT  DIRECTORY              NAME  SOURCE  STATUS    PROCESS       ASSIGNED
3018  ~/code/alfagen/mercury  main  free    hijacked  docker-proxy  2026-02-26 14:17
3022  ~/code/alfagen/mercury  www   free    free      -             2026-03-01 15:27

! Port 3018: used by docker-proxy from ~/worktrees/feature/issue-2110-refactor-monkey-patch-initializers-payme

Detection logic

procInfo := port.GetPortProcess(alloc.Port)
if procInfo != nil && procInfo.Cwd != "" {
    if filepath.Clean(procInfo.Cwd) != filepath.Clean(alloc.Directory) {
        status = "hijacked"
    }
}

When procInfo.Cwd == "" (no root access to read /proc/PID/cwd) — keep busy, can't determine hijacked.

Feature 2: --doctor command

Diagnostic command with 5 checks. By default read-only, --fix auto-fixes safe issues.

Checks

# Check Detection Fix
1 Hijacked ports busy + procInfo.Cwd != alloc.Directory Suggest --forget + restart
2 Stale directories os.Stat(alloc.Directory)IsNotExist --fix: remove allocation
3 Orphan ports busy port in range with no allocation Suggest --scan
4 Stale external StatusExternal + port is now free --fix: remove allocation
5 Unlocked busy busy + not locked (vulnerable to hijack) Suggest --lock

Output format

Checking port allocations...

! Hijacked ports (allocated to X, used by process from Y):
  Port 3018: allocated to ~/code/alfagen/mercury, used by docker-proxy
    from ~/worktrees/feature/issue-2110-...
  Fix: port-selector --forget (from ~/code/alfagen/mercury), then re-run

! Stale directories (directory no longer exists):
  Port 3005: ~/code/alfagen/mercury/chore/1988-universe
  Fix: run with --fix to clean up

! Orphan ports (busy but not tracked):
  Port 8080: used by node (pid=12345)
  Fix: port-selector --scan

! Stale external allocations (port now free):
  Port 3099: was used by docker-proxy
  Fix: run with --fix to clean up

! Unlocked busy ports (vulnerable to hijacking):
  Port 3022: ~/code/alfagen/mercury (ruby)
  Fix: port-selector --lock

Summary: 5 issues found (2 auto-fixable with --fix)

--fix behavior

  • Auto-fixes checks 2 (stale dirs) and 4 (stale external) — safe deletions of clearly invalid data
  • Only suggests for checks 1, 3, 5 — require user decision

Implementation plan

Files to modify:

  • cmd/port-selector/main.go — add --doctor case in main(), new runDoctor(fix bool), hijacked detection in runList(), parseFixFromArgs(), update printHelp()

Tests:

  • TestRunList_HijackedStatus — allocate port to dir A, occupy from dir B → verify "hijacked"
  • TestRunDoctor_StaleDirectory — allocate to non-existent dir → verify detection
  • TestRunDoctor_StaleExternal — external allocation for free port → verify detection
  • TestRunDoctor_UnlockedBusy — unlocked busy port → verify detection
  • TestRunDoctor_Fix — stale allocations → --fix → verify cleanup
  • TestRunDoctor_NoIssues — clean state → "No issues found"

Usage

port-selector --doctor          # Diagnose issues (read-only)
port-selector --doctor --fix    # Auto-fix safe issues

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