Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3fb58ca75 | |||
| 6a63a8d69c | |||
| 1ac0db25e5 |
@@ -10,7 +10,7 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "7.1.10",
|
||||
"version": "7.1.11",
|
||||
"source": "./plugin",
|
||||
"description": "Persistent memory system for Claude Code - context compression across sessions"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,42 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [7.1.10] - 2025-12-14
|
||||
|
||||
## Enhancement
|
||||
|
||||
This release adds automatic orphan cleanup to complement the process leak fix from v7.1.9.
|
||||
|
||||
### Added
|
||||
|
||||
- **Auto-Cleanup on Startup**: Worker now automatically detects and kills orphaned chroma-mcp processes before starting
|
||||
- Scans for existing chroma-mcp processes on worker startup
|
||||
- Kills all found processes before creating new ones
|
||||
- Logs cleanup activity (process count and PIDs)
|
||||
- Non-fatal error handling (continues on cleanup failure)
|
||||
|
||||
### Benefits
|
||||
|
||||
- Automatically recovers from pre-7.1.9 process leaks without manual intervention
|
||||
- Ensures clean slate on every worker restart
|
||||
- Prevents accumulation even if v7.1.9's close() method fails
|
||||
- No user action required - works transparently
|
||||
|
||||
### Example Logs
|
||||
|
||||
```
|
||||
[INFO] [SYSTEM] Cleaning up orphaned chroma-mcp processes {count=2, pids=33753,33750}
|
||||
[INFO] [SYSTEM] Orphaned processes cleaned up {count=2}
|
||||
```
|
||||
|
||||
### Recommendation
|
||||
|
||||
Upgrade from v7.1.9 to get automatic orphan cleanup. Combined with v7.1.9's proper subprocess cleanup, this provides comprehensive protection against process leaks.
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.9...v7.1.10
|
||||
|
||||
## [7.1.9] - 2025-12-14
|
||||
|
||||
## Critical Bugfix
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future sessions.
|
||||
|
||||
**Current Version**: 7.1.10
|
||||
**Current Version**: 7.1.11
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
Generated
-3882
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "7.1.10",
|
||||
"version": "7.1.11",
|
||||
"description": "Memory compression system for Claude Code - persist context across sessions",
|
||||
"keywords": [
|
||||
"claude",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "7.1.10",
|
||||
"version": "7.1.11",
|
||||
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
|
||||
"author": {
|
||||
"name": "Alex Newman"
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js\" && bun \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js\" && node \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js\"",
|
||||
"timeout": 300
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
@@ -23,7 +23,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js\"",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
@@ -35,7 +35,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js\"",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
@@ -46,7 +46,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js\"",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
@@ -57,7 +57,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js\"",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11
-11
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,9 +1,10 @@
|
||||
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
|
||||
import { createWriteStream } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { spawn, spawnSync } from 'child_process';
|
||||
import { spawn } from 'child_process';
|
||||
import { homedir } from 'os';
|
||||
import { DATA_DIR } from '../../shared/paths.js';
|
||||
import { getBunPath, isBunAvailable } from '../../utils/bun-path.js';
|
||||
|
||||
const PID_FILE = join(DATA_DIR, 'worker.pid');
|
||||
const LOG_DIR = join(DATA_DIR, 'logs');
|
||||
@@ -56,26 +57,22 @@ export class ProcessManager {
|
||||
}
|
||||
|
||||
private static isBunAvailable(): boolean {
|
||||
try {
|
||||
const result = spawnSync('bun', ['--version'], { stdio: 'pipe', timeout: 5000 });
|
||||
return result.status === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return isBunAvailable();
|
||||
}
|
||||
|
||||
private static async startWithBun(script: string, logFile: string, port: number): Promise<{ success: boolean; pid?: number; error?: string }> {
|
||||
if (!this.isBunAvailable()) {
|
||||
const bunPath = getBunPath();
|
||||
if (!bunPath) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Bun is required but not found in PATH. Install from https://bun.sh'
|
||||
error: 'Bun is required but not found in PATH or common installation paths. Install from https://bun.sh'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
const child = spawn('bun', [script], {
|
||||
const child = spawn(bunPath, [script], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: { ...process.env, CLAUDE_MEM_WORKER_PORT: String(port) },
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Bun Path Utility
|
||||
*
|
||||
* Resolves the Bun executable path for environments where Bun is not in PATH
|
||||
* (e.g., fish shell users where ~/.config/fish/config.fish isn't read by /bin/sh)
|
||||
*/
|
||||
|
||||
import { spawnSync } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
|
||||
/**
|
||||
* Get the Bun executable path
|
||||
* Tries PATH first, then checks common installation locations
|
||||
* Returns absolute path if found, null otherwise
|
||||
*/
|
||||
export function getBunPath(): string | null {
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
// Try PATH first
|
||||
try {
|
||||
const result = spawnSync('bun', ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: isWindows
|
||||
});
|
||||
if (result.status === 0) {
|
||||
return 'bun'; // Available in PATH
|
||||
}
|
||||
} catch {
|
||||
// Not in PATH, continue to check common locations
|
||||
}
|
||||
|
||||
// Check common installation paths
|
||||
const bunPaths = isWindows
|
||||
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
|
||||
: [
|
||||
join(homedir(), '.bun', 'bin', 'bun'),
|
||||
'/usr/local/bin/bun',
|
||||
'/opt/homebrew/bin/bun', // Apple Silicon Homebrew
|
||||
'/home/linuxbrew/.linuxbrew/bin/bun' // Linux Homebrew
|
||||
];
|
||||
|
||||
for (const bunPath of bunPaths) {
|
||||
if (existsSync(bunPath)) {
|
||||
return bunPath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Bun executable path or throw an error
|
||||
* Use this when Bun is required for operation
|
||||
*/
|
||||
export function getBunPathOrThrow(): string {
|
||||
const bunPath = getBunPath();
|
||||
if (!bunPath) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const installCmd = isWindows
|
||||
? 'powershell -c "irm bun.sh/install.ps1 | iex"'
|
||||
: 'curl -fsSL https://bun.sh/install | bash';
|
||||
throw new Error(
|
||||
`Bun is required but not found. Install it with:\n ${installCmd}\nThen restart your terminal.`
|
||||
);
|
||||
}
|
||||
return bunPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Bun is available (in PATH or common locations)
|
||||
*/
|
||||
export function isBunAvailable(): boolean {
|
||||
return getBunPath() !== null;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { existsSync } from 'fs';
|
||||
import { spawnSync } from 'child_process';
|
||||
|
||||
// Mock the dependencies
|
||||
vi.mock('fs', () => ({
|
||||
existsSync: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('child_process', () => ({
|
||||
spawnSync: vi.fn()
|
||||
}));
|
||||
|
||||
// Import after mocking
|
||||
import { getBunPath, isBunAvailable, getBunPathOrThrow } from '../src/utils/bun-path';
|
||||
|
||||
describe('bun-path utility', () => {
|
||||
it('should return "bun" when available in PATH', () => {
|
||||
// Mock successful bun --version check
|
||||
vi.mocked(spawnSync).mockReturnValue({
|
||||
status: 0,
|
||||
stdout: Buffer.from('1.0.0'),
|
||||
stderr: Buffer.from(''),
|
||||
pid: 1234,
|
||||
output: [],
|
||||
signal: null
|
||||
} as any);
|
||||
|
||||
const result = getBunPath();
|
||||
expect(result).toBe('bun');
|
||||
expect(spawnSync).toHaveBeenCalledWith('bun', ['--version'], expect.any(Object));
|
||||
});
|
||||
|
||||
it('should check common installation paths when not in PATH', () => {
|
||||
// Mock failed PATH check
|
||||
vi.mocked(spawnSync).mockReturnValue({
|
||||
status: 1,
|
||||
stdout: Buffer.from(''),
|
||||
stderr: Buffer.from(''),
|
||||
pid: 1234,
|
||||
output: [],
|
||||
signal: null
|
||||
} as any);
|
||||
|
||||
// Mock existsSync to return true for ~/.bun/bin/bun
|
||||
vi.mocked(existsSync).mockImplementation((path: any) => {
|
||||
return path.includes('.bun/bin/bun');
|
||||
});
|
||||
|
||||
const result = getBunPath();
|
||||
expect(result).toContain('.bun/bin/bun');
|
||||
});
|
||||
|
||||
it('should return null when bun is not found anywhere', () => {
|
||||
// Mock failed PATH check
|
||||
vi.mocked(spawnSync).mockReturnValue({
|
||||
status: 1,
|
||||
stdout: Buffer.from(''),
|
||||
stderr: Buffer.from(''),
|
||||
pid: 1234,
|
||||
output: [],
|
||||
signal: null
|
||||
} as any);
|
||||
|
||||
// Mock existsSync to always return false
|
||||
vi.mocked(existsSync).mockReturnValue(false);
|
||||
|
||||
const result = getBunPath();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return true for isBunAvailable when bun is found', () => {
|
||||
// Mock successful bun check
|
||||
vi.mocked(spawnSync).mockReturnValue({
|
||||
status: 0,
|
||||
stdout: Buffer.from('1.0.0'),
|
||||
stderr: Buffer.from(''),
|
||||
pid: 1234,
|
||||
output: [],
|
||||
signal: null
|
||||
} as any);
|
||||
|
||||
const result = isBunAvailable();
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw error in getBunPathOrThrow when bun not found', () => {
|
||||
// Mock failed bun check
|
||||
vi.mocked(spawnSync).mockReturnValue({
|
||||
status: 1,
|
||||
stdout: Buffer.from(''),
|
||||
stderr: Buffer.from(''),
|
||||
pid: 1234,
|
||||
output: [],
|
||||
signal: null
|
||||
} as any);
|
||||
vi.mocked(existsSync).mockReturnValue(false);
|
||||
|
||||
expect(() => getBunPathOrThrow()).toThrow('Bun is required');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user