kill-orphaned-typechecks.sh4.1 KBView on GitHub
#!/usr/bin/env bash
# Kill leftover agent typechecks and abandoned vitest runs that shells often leave behind.
# Safe: does NOT touch Cursor/VS Code tsserver, or live vite/esbuild for this repo's
# currently-running dev server (esbuild tied to a live parent is left alone).
#
# SCOPED TO THIS CHECKOUT. Every pattern is anchored to the repo root this script lives in,
# because these are `pgrep -f` matches against the whole command line and several checkouts of
# this repo are normally open at once (cedar-mail-1, cedar-mail-2, a worktree, …). Unanchored,
# running cleanup in one of them kills the deliberate `vitest`/`tsc` another one is mid-way
# through — the exact failure this script exists to prevent, aimed at a colleague instead of a
# leftover. Node resolves these binaries through <root>/node_modules/.pnpm/…, so the root is
# present in argv for anything actually launched from here.
#
# Usage: pnpm cleanup:orphans          # this checkout only
#        pnpm cleanup:orphans --all    # every checkout on the machine (old behaviour)
# Policy: AGENTS.md → Typecheck / Vitest hygiene; .cursor/rules/typecheck-hygiene.mdc
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

SCOPE_RE="$REPO_ROOT"
SCOPE_LABEL="in $REPO_ROOT"
if [[ "${1:-}" == "--all" ]]; then
  SCOPE_RE=""
  SCOPE_LABEL="machine-wide"
fi
echo "Scope: $SCOPE_LABEL"

killed=0

# `pgrep -f` takes an ERE, so the scope prefix composes straight onto the pattern.
scoped() { printf '%s.*%s' "$SCOPE_RE" "$1"; }

kill_matching() {
  local pattern
  pattern="$(scoped "$1")"
  local label="$2"
  local pids
  pids=$(pgrep -f "$pattern" 2>/dev/null || true)
  if [[ -z "$pids" ]]; then
    return 0
  fi
  echo "Killing $label:"
  # shellcheck disable=SC2086
  ps -o pid,etime,rss,command -p $pids 2>/dev/null || true
  # shellcheck disable=SC2086
  kill $pids 2>/dev/null || true
  sleep 1
  local survivors
  survivors=$(pgrep -f "$pattern" 2>/dev/null || true)
  if [[ -n "$survivors" ]]; then
    # shellcheck disable=SC2086
    kill -9 $survivors 2>/dev/null || true
  fi
  killed=$((killed + 1))
}

# One-shot agent typechecks (Claude/Cursor shell leftovers)
kill_matching 'typescript/bin/tsc --noEmit' 'tsc --noEmit'
kill_matching 'typescript/bin/tsc -b' 'tsc -b'
kill_matching 'typescript/lib/tsc.js --noEmit' 'tsc.js --noEmit'
kill_matching 'typescript/lib/tsc.js -b' 'tsc.js -b'

# Abandoned vitest runs — full-suite fork workers are multi-GB. Safe to kill from cleanup:
# intentional interactive runs should not need this script mid-flight.
kill_matching 'vitest/vitest.mjs' 'vitest main'
kill_matching 'vitest/dist/workers/forks.js' 'vitest fork workers'
kill_matching 'vitest/dist/workers/threads.js' 'vitest thread workers'

# Orphaned esbuild service processes whose parent is gone (common after a killed vite).
# Only kill if PPID is 1 (reparented to launchd/init) — live vite children have a real parent.
# Same checkout scoping as above: `root` is passed to awk rather than baked into the regex so a
# path containing regex metacharacters still matches literally (index(), not ~).
find_orphaned_esbuild() {
  ps -axo pid=,ppid=,command= 2>/dev/null \
    | awk -v root="$SCOPE_RE" \
        '/\/@esbuild\/darwin-arm64\/bin\/esbuild --service/ && $2 == 1 \
           && (root == "" || index($0, root)) { print $1 }' \
    || true
}

orphaned_esbuild=$(find_orphaned_esbuild)
if [[ -n "${orphaned_esbuild}" ]]; then
  echo "Killing orphaned esbuild (ppid=1):"
  # shellcheck disable=SC2086
  ps -o pid,ppid,etime,rss,command -p $orphaned_esbuild 2>/dev/null || true
  # shellcheck disable=SC2086
  kill $orphaned_esbuild 2>/dev/null || true
  sleep 1
  survivors=$(find_orphaned_esbuild)
  if [[ -n "${survivors}" ]]; then
    # shellcheck disable=SC2086
    kill -9 $survivors 2>/dev/null || true
  fi
  killed=$((killed + 1))
fi

if [[ "$killed" -eq 0 ]]; then
  echo "No orphaned typechecks/vitest/esbuild found."
else
  echo "Cleanup done."
fi

# Quick memory hint
if free_pages=$(vm_stat 2>/dev/null | awk '/Pages free/ {gsub(/\./,"",$3); print $3}'); then
  echo "Free pages ≈ $(( free_pages * 16384 / 1024 / 1024 )) MB"
fi