mirror of
https://github.com/thedotmack/claude-mem
synced 2026-04-25 17:15:04 +02:00
* fix: add idle timeout to prevent zombie observer processes Root cause fix for zombie observer accumulation. The SessionQueueProcessor iterator now exits gracefully after 3 minutes of inactivity instead of waiting forever for messages. Changes: - Add IDLE_TIMEOUT_MS constant (3 minutes) - waitForMessage() now returns boolean and accepts timeout parameter - createIterator() tracks lastActivityTime and exits on idle timeout - Graceful exit via return (not throw) allows SDK to complete cleanly This addresses the root cause that PR #848 worked around with pattern matching. Observer processes now self-terminate, preventing accumulation when session-complete hooks don't fire. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: trigger abort on idle timeout to actually kill subprocess The previous implementation only returned from the iterator on idle timeout, but this doesn't terminate the Claude subprocess - it just stops yielding messages. The subprocess stays alive as a zombie because: 1. Returning from createIterator() ends the generator 2. The SDK closes stdin via transport.endInput() 3. But the subprocess may not exit on stdin EOF 4. No abort signal is sent to kill it Fix: Add onIdleTimeout callback that SessionManager uses to call session.abortController.abort(). This sends SIGTERM to the subprocess via the SDK's ProcessTransport abort handler. Verified by Codex analysis of the SDK internals: - abort() triggers ProcessTransport abort handler → SIGTERM - transport.close() sends SIGTERM → escalates to SIGKILL after 5s - Just closing stdin is NOT sufficient to guarantee subprocess exit Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add idle timeout to prevent zombie observer processes Also cleaned up hooks.json to remove redundant start commands. The hook command handler now auto-starts the worker if not running, which is how it should have been since we changed to auto-start. This maintenance change was done manually. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: resolve race condition in session queue idle timeout detection - Reset timer on spurious wakeup when queue is empty but duration check fails - Use optional chaining for onIdleTimeout callback - Include threshold value in idle timeout log message for better diagnostics - Add comprehensive unit tests for SessionQueueProcessor Fixes PR #856 review feedback. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: migrate installer to Setup hook - Add plugin/scripts/setup.sh for one-time dependency setup - Add Setup hook to hooks.json (triggers via claude --init) - Remove smart-install.js from SessionStart hook - Keep smart-install.js as manual fallback for Windows/auto-install Setup hook handles: - Bun detection with fallback locations - uv detection (optional, for Chroma) - Version marker to skip redundant installs - Clear error messages with install instructions * feat: add np for one-command npm releases - Add np as dev dependency - Add release, release:patch, release:minor, release:major scripts - Add prepublishOnly hook to run build before publish - Configure np (no yarn, include all contents, run tests) * fix: reduce PostToolUse hook timeout to 30s PostToolUse runs on every tool call, 120s was excessive and could cause hangs. Reduced to 30s for responsive behavior. * docs: add PR shipping report Analyzed 6 PRs for shipping readiness: - #856: Ready to merge (idle timeout fix) - #700, #722, #657: Have conflicts, need rebase - #464: Contributor PR, too large (15K+ lines) - #863: Needs manual review Includes shipping strategy and conflict resolution order. * MAESTRO: Verify PR #856 test suite passes All 797 tests pass (3 skipped, 0 failures). The 11 SessionQueueProcessor idle timeout tests all pass with 20 expect() assertions verified. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * MAESTRO: Verify PR #856 build passes - Ran npm run build successfully with no TypeScript errors - All artifacts generated (worker-service, mcp-server, context-generator, viewer UI) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * MAESTRO: Code review PR #856 implementation verified Verified all requirements in SessionQueueProcessor.ts: - IDLE_TIMEOUT_MS = 180000ms (3 minutes) - waitForMessage() accepts timeout parameter - lastActivityTime reset on spurious wakeup (race condition fix) - Graceful exit logs include thresholdMs parameter - 11 comprehensive test cases in SessionQueueProcessor.test.ts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: bigph00t <166455923+bigph00t@users.noreply.github.com> Co-authored-by: root <root@srv1317155.hstgr.cloud>
229 lines
4.8 KiB
Bash
Executable File
229 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# claude-mem Setup Hook
|
||
# Ensures dependencies are installed before plugin runs
|
||
#
|
||
|
||
set -euo pipefail
|
||
|
||
# Use CLAUDE_PLUGIN_ROOT if available, otherwise detect from script location
|
||
if [[ -z "${CLAUDE_PLUGIN_ROOT:-}" ]]; then
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
ROOT="$(dirname "$SCRIPT_DIR")"
|
||
else
|
||
ROOT="$CLAUDE_PLUGIN_ROOT"
|
||
fi
|
||
|
||
MARKER="$ROOT/.install-version"
|
||
PKG_JSON="$ROOT/package.json"
|
||
|
||
# Colors (when terminal supports it)
|
||
if [[ -t 2 ]]; then
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[0;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
else
|
||
RED='' GREEN='' YELLOW='' BLUE='' NC=''
|
||
fi
|
||
|
||
log_info() { echo -e "${BLUE}ℹ${NC} $*" >&2; }
|
||
log_ok() { echo -e "${GREEN}✓${NC} $*" >&2; }
|
||
log_warn() { echo -e "${YELLOW}⚠${NC} $*" >&2; }
|
||
log_error() { echo -e "${RED}✗${NC} $*" >&2; }
|
||
|
||
#
|
||
# Detect Bun - check PATH and common locations
|
||
#
|
||
find_bun() {
|
||
# Try PATH first
|
||
if command -v bun &>/dev/null; then
|
||
echo "bun"
|
||
return 0
|
||
fi
|
||
|
||
# Check common install locations
|
||
local paths=(
|
||
"$HOME/.bun/bin/bun"
|
||
"/usr/local/bin/bun"
|
||
"/opt/homebrew/bin/bun"
|
||
)
|
||
|
||
for p in "${paths[@]}"; do
|
||
if [[ -x "$p" ]]; then
|
||
echo "$p"
|
||
return 0
|
||
fi
|
||
done
|
||
|
||
return 1
|
||
}
|
||
|
||
#
|
||
# Detect uv - check PATH and common locations
|
||
#
|
||
find_uv() {
|
||
# Try PATH first
|
||
if command -v uv &>/dev/null; then
|
||
echo "uv"
|
||
return 0
|
||
fi
|
||
|
||
# Check common install locations
|
||
local paths=(
|
||
"$HOME/.local/bin/uv"
|
||
"$HOME/.cargo/bin/uv"
|
||
"/usr/local/bin/uv"
|
||
"/opt/homebrew/bin/uv"
|
||
)
|
||
|
||
for p in "${paths[@]}"; do
|
||
if [[ -x "$p" ]]; then
|
||
echo "$p"
|
||
return 0
|
||
fi
|
||
done
|
||
|
||
return 1
|
||
}
|
||
|
||
#
|
||
# Get package.json version
|
||
#
|
||
get_pkg_version() {
|
||
if [[ -f "$PKG_JSON" ]]; then
|
||
# Simple grep-based extraction (no jq dependency)
|
||
grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$PKG_JSON" | head -1 | sed 's/.*"\([^"]*\)"$/\1/'
|
||
fi
|
||
}
|
||
|
||
#
|
||
# Get marker version (if exists)
|
||
#
|
||
get_marker_version() {
|
||
if [[ -f "$MARKER" ]]; then
|
||
grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$MARKER" | head -1 | sed 's/.*"\([^"]*\)"$/\1/'
|
||
fi
|
||
}
|
||
|
||
#
|
||
# Get marker's recorded bun version
|
||
#
|
||
get_marker_bun() {
|
||
if [[ -f "$MARKER" ]]; then
|
||
grep -o '"bun"[[:space:]]*:[[:space:]]*"[^"]*"' "$MARKER" | head -1 | sed 's/.*"\([^"]*\)"$/\1/'
|
||
fi
|
||
}
|
||
|
||
#
|
||
# Check if install is needed
|
||
#
|
||
needs_install() {
|
||
# No node_modules? Definitely need install
|
||
if [[ ! -d "$ROOT/node_modules" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
# No marker? Need install
|
||
if [[ ! -f "$MARKER" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
local pkg_ver marker_ver bun_ver marker_bun
|
||
pkg_ver=$(get_pkg_version)
|
||
marker_ver=$(get_marker_version)
|
||
|
||
# Version mismatch? Need install
|
||
if [[ "$pkg_ver" != "$marker_ver" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
# Bun version changed? Need install
|
||
if BUN_PATH=$(find_bun); then
|
||
bun_ver=$("$BUN_PATH" --version 2>/dev/null || echo "")
|
||
marker_bun=$(get_marker_bun)
|
||
if [[ -n "$bun_ver" && "$bun_ver" != "$marker_bun" ]]; then
|
||
return 0
|
||
fi
|
||
fi
|
||
|
||
# All good, no install needed
|
||
return 1
|
||
}
|
||
|
||
#
|
||
# Write version marker after successful install
|
||
#
|
||
write_marker() {
|
||
local bun_ver uv_ver pkg_ver
|
||
pkg_ver=$(get_pkg_version)
|
||
bun_ver=$("$BUN_PATH" --version 2>/dev/null || echo "unknown")
|
||
|
||
if UV_PATH=$(find_uv); then
|
||
uv_ver=$("$UV_PATH" --version 2>/dev/null | head -1 || echo "unknown")
|
||
else
|
||
uv_ver="not-installed"
|
||
fi
|
||
|
||
cat > "$MARKER" <<EOF
|
||
{
|
||
"version": "$pkg_ver",
|
||
"bun": "$bun_ver",
|
||
"uv": "$uv_ver",
|
||
"installedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||
}
|
||
EOF
|
||
}
|
||
|
||
#
|
||
# Main
|
||
#
|
||
|
||
# 1. Check for Bun
|
||
BUN_PATH=$(find_bun) || true
|
||
if [[ -z "$BUN_PATH" ]]; then
|
||
log_error "Bun runtime not found!"
|
||
echo "" >&2
|
||
echo "claude-mem requires Bun to run. Please install it:" >&2
|
||
echo "" >&2
|
||
echo " curl -fsSL https://bun.sh/install | bash" >&2
|
||
echo "" >&2
|
||
echo "Or on macOS with Homebrew:" >&2
|
||
echo "" >&2
|
||
echo " brew install oven-sh/bun/bun" >&2
|
||
echo "" >&2
|
||
echo "Then restart your terminal and try again." >&2
|
||
exit 1
|
||
fi
|
||
|
||
BUN_VERSION=$("$BUN_PATH" --version 2>/dev/null || echo "unknown")
|
||
log_ok "Bun $BUN_VERSION found at $BUN_PATH"
|
||
|
||
# 2. Check for uv (optional - for Python/Chroma support)
|
||
UV_PATH=$(find_uv) || true
|
||
if [[ -z "$UV_PATH" ]]; then
|
||
log_warn "uv not found (optional - needed for Python/Chroma vector search)"
|
||
echo " To install: curl -LsSf https://astral.sh/uv/install.sh | sh" >&2
|
||
else
|
||
UV_VERSION=$("$UV_PATH" --version 2>/dev/null | head -1 || echo "unknown")
|
||
log_ok "uv $UV_VERSION found"
|
||
fi
|
||
|
||
# 3. Install dependencies if needed
|
||
if needs_install; then
|
||
log_info "Installing dependencies with Bun..."
|
||
|
||
if ! "$BUN_PATH" install --cwd "$ROOT"; then
|
||
log_error "Failed to install dependencies"
|
||
exit 1
|
||
fi
|
||
|
||
write_marker
|
||
log_ok "Dependencies installed ($(get_pkg_version))"
|
||
else
|
||
log_ok "Dependencies up to date ($(get_marker_version))"
|
||
fi
|
||
|
||
exit 0
|