Skip to content

fix: stale backend holding port blocks restart (reap own unhealthy listener; Windows netstat/taskkill fallback)#116

Open
smoochy wants to merge 2 commits into
ReflexioAI:mainfrom
smoochy:fix/issue-108-stale-backend-reap
Open

fix: stale backend holding port blocks restart (reap own unhealthy listener; Windows netstat/taskkill fallback)#116
smoochy wants to merge 2 commits into
ReflexioAI:mainfrom
smoochy:fix/issue-108-stale-backend-reap

Conversation

@smoochy

@smoochy smoochy commented Jul 6, 2026

Copy link
Copy Markdown

In start: reap our own unhealthy holder and retry before skipping. Add netstat/tasklist/taskkill fallback when lsof is absent. Closes #108.

Summary by CodeRabbit

  • Bug Fixes
    • Improved backend startup on Windows by more reliably detecting when the target port is already in use.
    • Added Windows-friendly fallbacks to identify and stop the likely stale backend listener processes when common Unix tools aren’t available, then retry startup.
    • Enhanced port diagnostics to report which process is holding the port even in limited environments (Windows-friendly process inspection).

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fb59195b-806a-41d4-9a81-0d6cf39b6ba1

📥 Commits

Reviewing files that changed from the base of the PR and between 7f39cff and 2a590d6.

📒 Files selected for processing (1)
  • plugin/scripts/backend-service.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugin/scripts/backend-service.sh

📝 Walkthrough

Walkthrough

This change adds Windows-compatible fallback logic to backend-service.sh for detecting and terminating processes holding a TCP port when lsof is unavailable, using netstat, tasklist, and taskkill. The start flow now reaps stale reflexio/uvicorn/python holders before giving up on port conflicts.

Changes

Backend service Windows reap support

Layer / File(s) Summary
Windows port/PID helper functions
plugin/scripts/backend-service.sh
Adds port_pids_netstat() to list PIDs listening on a port via netstat -ano, and pid_cmdline_win() to resolve a PID's command name via tasklist.
reap_port_listeners Windows fallback
plugin/scripts/backend-service.sh
Falls back to the new helpers for candidate PID discovery and command matching when lsof or ps are unavailable; force-kills matched PIDs via taskkill when lsof is absent and taskkill is present.
port_holder Windows fallback
plugin/scripts/backend-service.sh
Updates the diagnostic port_holder routine to derive PID via netstat and format the holder description via tasklist when lsof is unavailable.
start command stale-holder reap and retry
plugin/scripts/backend-service.sh
When the port is occupied and the holder matches *reflexio*, *uvicorn*, or *python*, reaps the listeners and rechecks port availability before deciding to skip start.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Start as start command
    participant PortCheck as port_occupied/port_holder
    participant Reap as reap_port_listeners
    participant OS as netstat/tasklist/taskkill

    Start->>PortCheck: check if port occupied
    PortCheck->>OS: query port_pids_netstat / pid_cmdline_win
    OS-->>PortCheck: PID and command info
    PortCheck-->>Start: holder description

    alt holder matches reflexio/uvicorn/python
        Start->>Reap: reap_port_listeners
        Reap->>OS: port_pids_netstat / pid_cmdline_win
        Reap->>OS: taskkill /F matched PIDs
        Reap-->>Start: listeners terminated
        Start->>PortCheck: re-check port availability
        PortCheck-->>Start: port free or still occupied
    else holder is foreign process
        Start-->>Start: log and skip start
    end
Loading

Related Issues: #108 (stale backend holding port blocks restart; lsof-gated reap no-ops on Windows)

Suggested labels: bug, windows, backend-service

Suggested reviewers: backend-service maintainers familiar with shell process handling

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the stale-backend restart fix and Windows fallback.
Linked Issues check ✅ Passed The PR addresses the linked restart bug by reaping matching stale listeners on start and adding Windows netstat/tasklist/taskkill fallbacks.
Out of Scope Changes check ✅ Passed The changes stay focused on backend-service restart and process-reaping behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

Actionable comments posted: 4

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

Inline comments:
In `@plugin/scripts/backend-service.sh`:
- Around line 233-237: The PID command lookup in backend-service.sh should fall
back to pid_cmdline_win when the ps-based cmdline lookup returns empty, not only
when ps is unavailable. Update the stale-listener matching logic around the
pid/cmdline resolution so the command line is populated from pid_cmdline_win
whenever ps -p "$pid" -o command= yields no result, keeping the existing command
detection flow intact.
- Around line 207-214: The Windows PID lookup in pid_cmdline_win currently
returns only the image name, which lets the generic python fallback match
unrelated processes. Update the backend-service.sh reaping flow so the Windows
path uses a real command line source or removes the broad *python* match and
only kills processes with project-specific markers. Keep the fix centered around
pid_cmdline_win and the stale backend detection logic that consumes its output.
- Around line 202-204: The PID extraction in the netstat pipeline still carries
Windows CRLF, which leaves a trailing carriage return on the value returned from
the listener lookup. Update the parsing in the backend-service.sh netstat/awk
flow so the emitted PID is normalized by stripping any trailing \r before it is
used by the stale-listener cleanup logic, ensuring the values match downstream
calls like tasklist, ps, and taskkill.
- Around line 246-248: The Windows-only direct taskkill path in
backend-service.sh should not bypass the existing kill-tree logic. Update the
branch in the shutdown/cleanup flow to reuse claude_smart_kill_tree for each PID
in ours so Git Bash slash escaping and child-process termination are handled
consistently, instead of calling taskkill /F /PID directly in the loop.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: af6d4f10-675d-4021-b4dd-b2bd75ba99de

📥 Commits

Reviewing files that changed from the base of the PR and between 69fccb0 and 7f39cff.

📒 Files selected for processing (1)
  • plugin/scripts/backend-service.sh

Comment on lines +202 to +204
netstat -ano 2>/dev/null | awk -v p=":$1\$" '
$1 ~ /^TCP/ && $2 ~ p && $4 == "LISTENING" { print $5 }
' | sort -u

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' "== plugin/scripts/backend-service.sh (around lines 195-210) =="
sed -n '190,215p' plugin/scripts/backend-service.sh | cat -n

printf '\n%s\n' "== Where port_pids_netstat is used =="
rg -n "port_pids_netstat|taskkill|tasklist|reap_port_listeners|port_holder|pid_cmdline_win" plugin/scripts/backend-service.sh plugin/scripts/_lib.sh

printf '\n%s\n' "== Relevant helper docs =="
sed -n '1,220p' plugin/scripts/_lib.sh | cat -n | sed -n '1,220p'

Repository: ReflexioAI/claude-smart

Length of output: 12950


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '220,380p' plugin/scripts/backend-service.sh | cat -n

printf '\n%s\n' "== Simulate netstat CRLF parsing with awk =="
python3 - <<'PY'
import subprocess, textwrap, os, json, sys, tempfile

sample = textwrap.dedent("""\
TCP    127.0.0.1:8080    0.0.0.0:0    LISTENING    1234\r
TCP    127.0.0.1:8080    0.0.0.0:0    LISTENING    5678\r
""")

script = r'''awk -v p=":8080$" '
  $1 ~ /^TCP/ && $2 ~ p && $4 == "LISTENING" { print $5 }
' | sort -u'''
proc = subprocess.run(
    ["bash", "-lc", script],
    input=sample.encode(),
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
)
print("stdout repr:", repr(proc.stdout.decode()))
print("stderr repr:", repr(proc.stderr.decode()))
PY

Repository: ReflexioAI/claude-smart

Length of output: 7778


Strip \r from Windows netstat PIDs.
netstat -ano on Git Bash leaves CRLF on $5, so the PID becomes 1234\r and won’t match tasklist, ps, or taskkill. That breaks stale-listener cleanup on Windows.

Proposed fix
-    $1 ~ /^TCP/ && $2 ~ p && $4 == "LISTENING" { print $5 }
+    $1 ~ /^TCP/ && $2 ~ p && $4 == "LISTENING" { gsub(/\r$/, "", $5); print $5 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
netstat -ano 2>/dev/null | awk -v p=":$1\$" '
$1 ~ /^TCP/ && $2 ~ p && $4 == "LISTENING" { print $5 }
' | sort -u
netstat -ano 2>/dev/null | awk -v p=":$1\$" '
$1 ~ /^TCP/ && $2 ~ p && $4 == "LISTENING" { gsub(/\r$/, "", $5); print $5 }
' | sort -u
🤖 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 `@plugin/scripts/backend-service.sh` around lines 202 - 204, The PID extraction
in the netstat pipeline still carries Windows CRLF, which leaves a trailing
carriage return on the value returned from the listener lookup. Update the
parsing in the backend-service.sh netstat/awk flow so the emitted PID is
normalized by stripping any trailing \r before it is used by the stale-listener
cleanup logic, ensuring the values match downstream calls like tasklist, ps, and
taskkill.

Comment thread plugin/scripts/backend-service.sh
Comment thread plugin/scripts/backend-service.sh
Comment on lines +246 to +248
if command -v taskkill >/dev/null 2>&1 && ! command -v lsof >/dev/null 2>&1; then
# Windows: no POSIX signals to these PIDs; force-kill directly.
for pid in $ours; do taskkill /F /PID "$pid" >/dev/null 2>&1 || true; done

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use the shared Git Bash-safe kill helper.

This direct taskkill /F /PID path bypasses the existing claude_smart_kill_tree handling for Git Bash slash escaping and child-process termination. Reuse the helper instead.

Proposed fix
   if command -v taskkill >/dev/null 2>&1 && ! command -v lsof >/dev/null 2>&1; then
-    # Windows: no POSIX signals to these PIDs; force-kill directly.
-    for pid in $ours; do taskkill /F /PID "$pid" >/dev/null 2>&1 || true; done
+    # Windows: netstat returns native PIDs; shared helper handles taskkill safely.
+    for pid in $ours; do claude_smart_kill_tree "$pid"; done
     return 0
   fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if command -v taskkill >/dev/null 2>&1 && ! command -v lsof >/dev/null 2>&1; then
# Windows: no POSIX signals to these PIDs; force-kill directly.
for pid in $ours; do taskkill /F /PID "$pid" >/dev/null 2>&1 || true; done
if command -v taskkill >/dev/null 2>&1 && ! command -v lsof >/dev/null 2>&1; then
# Windows: netstat returns native PIDs; shared helper handles taskkill safely.
for pid in $ours; do claude_smart_kill_tree "$pid"; done
return 0
fi
🤖 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 `@plugin/scripts/backend-service.sh` around lines 246 - 248, The Windows-only
direct taskkill path in backend-service.sh should not bypass the existing
kill-tree logic. Update the branch in the shutdown/cleanup flow to reuse
claude_smart_kill_tree for each PID in ours so Git Bash slash escaping and
child-process termination are handled consistently, instead of calling taskkill
/F /PID directly in the loop.

fix: fall back to tasklist when Git Bash ps yields no cmdline

reap_port_listeners only consulted pid_cmdline_win when `ps` was entirely
absent. Git Bash ships `ps`, but it may not support `-o command=` or resolve a
native PID from netstat, leaving cmdline empty so a stale listener never matched
and was never reaped. Try tasklist whenever the ps lookup comes back empty.

Addresses CodeRabbit review on ReflexioAI#116.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
@smoochy

smoochy commented Jul 6, 2026

Copy link
Copy Markdown
Author

Thanks for the review — addressed in 2a590d6: reap_port_listeners now falls back to pid_cmdline_win (tasklist) whenever the ps -o command= lookup comes back empty, so Git Bash's ps no longer blocks stale-listener matching.

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.

Stale backend holding port blocks restart -> dashboard 502 (start doesn't reap own unhealthy listener; lsof-gated reap no-ops on Windows)

1 participant