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