diff --git a/openspec/changes/split-run-into-independent-repository/design.md b/openspec/changes/split-run-into-independent-repository/design.md index ba0c685..2b04b4c 100644 --- a/openspec/changes/split-run-into-independent-repository/design.md +++ b/openspec/changes/split-run-into-independent-repository/design.md @@ -16,6 +16,8 @@ git clone git@git.npc0.com:admin343/run.git run If `RUN_REPO_DIR` is missing or does not contain a run `go.mod`, local debug scripts fail with a clear setup message. +Local debug start/stop scripts treat pid files as hints, not the only source of truth. If a previous local debug process still owns the configured platform or web port after pid files are removed, the scripts identify it by cwd/log ownership under this repository and stop it before starting a new stack. + ## Docker Compose The compose file keeps the run service for all-in-one local deployment, but its build context points at `${RUN_REPO_DIR:-./run}` and uses the external repository's `Dockerfile`. diff --git a/openspec/changes/split-run-into-independent-repository/tasks.md b/openspec/changes/split-run-into-independent-repository/tasks.md index a123459..72ef3a1 100644 --- a/openspec/changes/split-run-into-independent-repository/tasks.md +++ b/openspec/changes/split-run-into-independent-repository/tasks.md @@ -3,6 +3,7 @@ - [x] Add OpenSpec proposal/design/spec for the repository split. - [x] Remove `run/` from browser-owned project roots and structure checks. - [x] Update README, local debug scripts, and Docker Compose to use `RUN_REPO_DIR`. +- [x] Harden local debug start/stop against stale owned listeners when pid files are missing. - [x] Remove tracked `run/` source files from `browser.git`. - [x] Run `scripts/check-structure.sh`. - [x] Run `openspec validate split-run-into-independent-repository --strict`. diff --git a/scripts/local-debug-start.sh b/scripts/local-debug-start.sh index b041d5c..bb79764 100755 --- a/scripts/local-debug-start.sh +++ b/scripts/local-debug-start.sh @@ -23,6 +23,24 @@ port_listener_pid() { lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | head -n 1 || true } +listener_owned_by_local_debug() { + local pid="$1" + local details + details="$(lsof -nP -p "$pid" 2>/dev/null || true)" + [[ "$details" == *"$ROOT_DIR"* || "$details" == *"$LOCAL_DEBUG_ROOT"* ]] +} + +wait_for_pid_exit() { + local pid="$1" + for _ in $(seq 1 20); do + if ! kill -0 "$pid" 2>/dev/null; then + return 0 + fi + sleep 0.25 + done + return 1 +} + ensure_port_available() { local name="$1" local port="$2" @@ -35,6 +53,17 @@ ensure_port_available() { if managed_pid_running "$pid_file" && [[ "$listener_pid" == "$(cat "$pid_file")" ]]; then return 0 fi + if listener_owned_by_local_debug "$listener_pid"; then + printf 'stopping stale %s listener pid %s on port %s\n' "$name" "$listener_pid" "$port" + kill "$listener_pid" 2>/dev/null || true + if ! wait_for_pid_exit "$listener_pid"; then + printf 'stale %s listener pid %s did not stop after SIGTERM; sending SIGKILL\n' "$name" "$listener_pid" + kill -9 "$listener_pid" 2>/dev/null || true + wait_for_pid_exit "$listener_pid" || true + fi + rm -f "$pid_file" + return 0 + fi printf '%s port %s is already used by pid %s; choose another LOCAL_DEBUG_*_PORT or stop the existing listener\n' "$name" "$port" "$listener_pid" >&2 return 1 } diff --git a/scripts/local-debug-stop.sh b/scripts/local-debug-stop.sh index 5c5acb0..388eacf 100755 --- a/scripts/local-debug-stop.sh +++ b/scripts/local-debug-stop.sh @@ -7,20 +7,63 @@ source "$ROOT_DIR/scripts/local-debug-env.sh" if [[ ! -d "$LOCAL_DEBUG_PID_DIR" ]]; then printf 'no local debug pid directory at %s\n' "$LOCAL_DEBUG_PID_DIR" - exit 0 +else + for name in platform_web run platform; do + pid_file="$LOCAL_DEBUG_PID_DIR/$name.pid" + if [[ ! -f "$pid_file" ]]; then + continue + fi + pid="$(cat "$pid_file")" + if kill -0 "$pid" 2>/dev/null; then + printf 'stopping %s pid %s\n' "$name" "$pid" + kill "$pid" 2>/dev/null || true + else + printf '%s pid %s is not running\n' "$name" "$pid" + fi + rm -f "$pid_file" + done fi -for name in platform_web run platform; do - pid_file="$LOCAL_DEBUG_PID_DIR/$name.pid" - if [[ ! -f "$pid_file" ]]; then - continue +wait_for_pid_exit() { + local pid="$1" + for _ in $(seq 1 20); do + if ! kill -0 "$pid" 2>/dev/null; then + return 0 + fi + sleep 0.25 + done + return 1 +} + +listener_owned_by_local_debug() { + local pid="$1" + local details + details="$(lsof -nP -p "$pid" 2>/dev/null || true)" + [[ "$details" == *"$ROOT_DIR"* || "$details" == *"$LOCAL_DEBUG_ROOT"* ]] +} + +stop_listener_on_port() { + local name="$1" + local port="$2" + local pid + pid="$(lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | head -n 1 || true)" + if [[ -z "$pid" ]]; then + return 0 fi - pid="$(cat "$pid_file")" - if kill -0 "$pid" 2>/dev/null; then - printf 'stopping %s pid %s\n' "$name" "$pid" + + if listener_owned_by_local_debug "$pid"; then + printf 'stopping %s listener pid %s on port %s\n' "$name" "$pid" "$port" kill "$pid" 2>/dev/null || true + if ! wait_for_pid_exit "$pid"; then + printf '%s listener pid %s did not stop after SIGTERM; sending SIGKILL\n' "$name" "$pid" + kill -9 "$pid" 2>/dev/null || true + wait_for_pid_exit "$pid" || true + fi + return 0 else - printf '%s pid %s is not running\n' "$name" "$pid" + printf '%s port %s is still used by pid %s outside local debug ownership\n' "$name" "$port" "$pid" >&2 fi - rm -f "$pid_file" -done +} + +stop_listener_on_port platform "$LOCAL_DEBUG_PLATFORM_PORT" +stop_listener_on_port platform_web "$LOCAL_DEBUG_WEB_PORT"