From 287e230639d7fe3512074ff119c52782a1aec50c Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Mon, 13 Jul 2026 17:39:21 -0700 Subject: [PATCH] neo: stop paying for the same MCP server seven times MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit neo went into swap death twice today — 7 Claude sessions, ~56 node processes, 6.2 GB of swap, and an sshd that could no longer be spawned to let anyone in and fix it. The sessions were never the problem. Every MCP server was configured stdio, so each session spawned its own private copy of all eight; http-front.mjs and four of the daemons already existed to fix exactly this, and nothing was ever pointed at them. memory-mcp defaulted to 7769 — puppet's port — so it could never have been daemonized alongside puppet anyway. Move it to 7772. claude-reap closes sessions that have gone quiet and prints the `--resume` line onto the terminal it just cleared, so the way back sits where the session used to be. Idleness is transcript mtime: Claude rewrites that file every turn, so only a session waiting on a human goes stale, and live work is never reaped. Same seven sessions now cost 9 node processes and no swap. Co-Authored-By: Claude Opus 4.8 (1M context) --- memory/memory-mcp.mjs | 2 +- slab/bin/claude-reap | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 slab/bin/claude-reap diff --git a/memory/memory-mcp.mjs b/memory/memory-mcp.mjs index 8acf6fd6ee..c8704923fb 100644 --- a/memory/memory-mcp.mjs +++ b/memory/memory-mcp.mjs @@ -306,6 +306,6 @@ async function handleMessage(message) { } } -const port = httpPort(process.argv, 7769); +const port = httpPort(process.argv, 7772); // 7769 is puppet's if (port) serveHttp({ handleMessage, port, banner: "🧠 memory-mcp shared daemon" }); else serveStdio({ handleMessage, banner: "🧠 memory-mcp server started (memory_search, memory_sessions, memory_machines)" }); diff --git a/slab/bin/claude-reap b/slab/bin/claude-reap new file mode 100644 index 0000000000..a6b90981f5 --- /dev/null +++ b/slab/bin/claude-reap @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# claude-reap — close Claude sessions that have gone quiet, and say so on their tty. +# +# neo is an 8 GB box. A finished session costs as much resident RAM as a working +# one, and the sessions that killed it twice were mostly *done* — last words +# "great", "yes", "compush now" — just sitting there. So: reap the quiet ones. +# +# Idleness is the mtime of the session's own transcript. Claude rewrites that +# file on every turn, so a session that is thinking, tool-calling, or streaming +# keeps touching it; only one waiting on a human goes stale. That makes mtime a +# truthful "nobody is here" signal, and means we never reap live work. +# +# Nothing is lost: the transcript IS the session. We print the `--resume` line +# onto the terminal we just cleared, so the way back is sitting where the +# session used to be. +# +# Usage: +# claude-reap # reap sessions idle >= IDLE_MINUTES (default 30) +# claude-reap 45 # ...with an explicit threshold +# claude-reap 30 dry # report only, kill nothing + +set -u + +THRESHOLD="${1:-${IDLE_MINUTES:-30}}" +DRY="${2:-}" +PROJECTS="$HOME/.claude/projects" +now=$(date +%s) + +for pid in $(pgrep -x claude); do + # The transcript this session holds open. Subagent transcripts live under + # subagents/ and belong to a parent that is still very much alive. + transcript=$(lsof -p "$pid" -Fn 2>/dev/null \ + | grep -m1 -o "${PROJECTS}/[^ ]*\.jsonl" \ + | grep -v '/subagents/') + [ -z "$transcript" ] && continue + + mtime=$(stat -f %m "$transcript" 2>/dev/null) || continue + idle=$(( (now - mtime) / 60 )) + [ "$idle" -lt "$THRESHOLD" ] && continue + + id=$(basename "$transcript" .jsonl) + tty=$(ps -o tty= -p "$pid" 2>/dev/null | tr -d ' ') + + if [ -n "$DRY" ]; then + echo "would reap ${id%%-*} (idle ${idle}m, tty ${tty:-?})" + continue + fi + + kill "$pid" 2>/dev/null || continue + + # Leave the way back where the session was, not in a log nobody reads. + if [ -n "$tty" ] && [ -w "/dev/$tty" ]; then + { + printf '\n\033[33m─── claude-reap ───\033[0m\n' + printf ' quiet for %sm, so it was closed to keep neo off swap.\n' "$idle" + printf ' the conversation is intact — pick it back up with:\n\n' + printf ' \033[36mclaude --resume %s\033[0m\n\n' "$id" + } > "/dev/$tty" 2>/dev/null + fi + + echo "reaped ${id%%-*} (idle ${idle}m, tty ${tty:-?})" +done -- 2.51.2