How Claude-Mem Handles Version Mismatch Between Plugin and Worker Service
Claude-Mem automatically detects version mismatches between its VS Code plugin and worker service by comparing the plugin's package.json version against the worker's baked-in build constant, triggering an automatic restart sequence when they differ.
The thedotmack/claude-mem repository implements a robust version synchronization system to ensure its dual-component architecture remains tightly coupled. Since the plugin and worker service run as separate processes, version drift could cause API incompatibilities or silent failures. The codebase implements a three-stage detection and resolution pipeline that guarantees version consistency without user intervention.
Understanding the Claude-Mem Architecture
Claude-Mem operates through two distinct components that must maintain version parity:
| Component | Runtime Location | Version Source |
|---|---|---|
| Plugin | VS Code/IDE process | Root package.json read at runtime |
| Worker Service | Separate Node/Bun HTTP daemon | Build-time esbuild define (__DEFAULT_PACKAGE_VERSION__) |
The worker exposes an HTTP API at /api/version that returns its embedded version string. When the plugin initializes or executes commands, it validates this value against its own runtime version before proceeding with operations.
How Version Mismatch Detection Works
Baking the Version into the Worker Service
During the build process, the version string from package.json is injected into the worker binary using esbuild's define feature. This creates an immutable version constant that persists for the lifetime of the worker process.
// src/services/worker-service.ts
declare const __DEFAULT_PACKAGE_VERSION__: string;
const packageVersion = typeof __DEFAULT_PACKAGE_VERSION__ !== 'undefined'
? __DEFAULT_PACKAGE_VERSION__
: '0.0.0-dev';
The build script scripts/verify-timestamp-fix.ts reads the semantic version from the root package.json and passes it to the esbuild configuration, ensuring the worker always knows its exact build version.
The Version Check Endpoint
The worker exposes a lightweight HTTP endpoint that returns the baked-in version:
// src/services/infrastructure/HealthMonitor.ts
export async function checkVersionMatch(port: number) {
const response = await fetch(`http://127.0.0.1:${port}/api/version`);
const data = await response.json() as { version: string };
const pluginVersion = packageJson.version; // read from root package.json
return {
matches: data.version === pluginVersion,
pluginVersion,
workerVersion: data.version,
};
}
This utility performs the actual comparison between the running worker's version and the plugin's current version.
Plugin-Side Validation
When the plugin needs to ensure the worker is available, it calls ensureWorkerStarted(port), which first checks for an existing worker on the expected port. If a worker is detected, it immediately performs the version validation:
// src/services/worker-service.ts
const versionCheck = await checkVersionMatch(port);
if (!versionCheck.matches) {
// Trigger restart sequence for version mismatch
}
This validation occurs during the initialization of all major plugin operations, preventing version drift from causing API incompatibilities mid-session.
Automatic Resolution of Version Mismatches
The Restart Sequence
When checkVersionMatch returns matches: false, the plugin executes an automatic recovery sequence rather than failing:
- Log the mismatch with detailed version information for debugging
- Shutdown the outdated worker via
httpShutdown(port) - Wait for port release using
waitForPortFreewith platform-specific timeouts - Clean up PID files to prevent stale process references
- Spawn a new daemon with
spawnDaemon(__filename, port)
// src/services/worker-service.ts
logger.info('SYSTEM', 'Worker version mismatch detected - auto-restarting', {
pluginVersion: versionCheck.pluginVersion,
workerVersion: versionCheck.workerVersion
});
await httpShutdown(port);
await waitForPortFree(port, getPlatformTimeout(HOOK_TIMEOUTS.PORT_IN_USE_WAIT));
removePidFile();
const pid = spawnDaemon(__filename, port);
The new worker process automatically inherits the correct version from the current plugin's build, ensuring synchronization.
Windows-Specific Safeguards
The codebase includes specific protections for Windows environments to prevent rapid respawn loops that could flood the system with cmd.exe dialogs. A lock file mechanism implements a cooldown period between restart attempts:
// src/services/worker-service.ts
// Windows spawn-cooldown logic prevents rapid respawn loops
if (process.platform === 'win32') {
const lockFile = path.join(os.tmpdir(), 'claude-mem-worker.lock');
// Check for recent lock file to prevent flooding cmd.exe windows
}
This safeguard ensures that even if version detection encounters edge cases on Windows, the system won't enter a destructive respawn cycle.
Practical Code Examples
Manually Checking Version from a Script
You can verify version compatibility programmatically using the health monitor utilities:
import { checkVersionMatch } from './src/services/infrastructure/HealthMonitor.js';
import { getWorkerPort } from './src/shared/worker-utils.js';
async function demo() {
const port = getWorkerPort();
const result = await checkVersionMatch(port);
console.log('Plugin version:', result.pluginVersion);
console.log('Worker version:', result.workerVersion);
console.log('Match?', result.matches);
}
demo();
Forcing a Restart When a Mismatch is Detected
To programmatically ensure version alignment, use the ensureWorkerStarted function:
import { ensureWorkerStarted } from './src/services/worker-service.js';
async function startOrRefresh() {
const port = getWorkerPort();
const ok = await ensureWorkerStarted(port);
if (!ok) {
console.error('Could not start a matching worker – aborting.');
process.exit(1);
}
}
startOrRefresh();
The Low-Level Version Match Utility
The core comparison logic resides in the HealthMonitor service:
// src/services/infrastructure/HealthMonitor.ts (excerpt)
export async function checkVersionMatch(port: number) {
const response = await fetch(`http://127.0.0.1:${port}/api/version`);
const data = await response.json() as { version: string };
const pluginVersion = packageJson.version; // read from root package.json at runtime
return {
matches: data.version === pluginVersion,
pluginVersion,
workerVersion: data.version,
};
}
Key Implementation Files
| File | Purpose | Direct Link |
|---|---|---|
src/services/worker-service.ts |
Core orchestrator; injects version, starts worker, performs mismatch detection & auto-restart | worker-service.ts |
src/services/infrastructure/HealthMonitor.ts |
Implements checkVersionMatch, waitForHealth, httpShutdown |
HealthMonitor.ts |
src/shared/worker-utils.ts |
Provides getWorkerPort, getWorkerHost used by the start-up logic |
worker-utils.ts |
package.json (root) |
Single source of truth for the semantic version string | package.json |
scripts/verify-timestamp-fix.ts |
Reads package.json.version and injects it into the worker via esbuild define |
verify-timestamp-fix.ts |
Summary
- Version mismatch detection relies on a build-time injection of
package.jsonversion into the worker via esbuild'sdefinefeature (__DEFAULT_PACKAGE_VERSION__), while the plugin reads the same version dynamically from its manifest. - Runtime validation occurs through the
/api/versionendpoint exposed by the worker, whichcheckVersionMatchinHealthMonitor.tsqueries to compare against the plugin's current version. - Automatic resolution triggers when
ensureWorkerStarteddetects a mismatch, executing a graceful shutdown of the outdated worker, waiting for port release, and spawning a fresh daemon with the correct version. - Cross-platform safety includes Windows-specific cooldown logic to prevent rapid respawn loops that could flood the system with command prompts.
Frequently Asked Questions
What happens if the worker service is running a newer version than the plugin?
The version mismatch detection is bidirectional—any difference between the plugin's package.json version and the worker's baked-in __DEFAULT_PACKAGE_VERSION__ triggers the auto-restart sequence. If the worker is newer, the plugin will shut it down and spawn a fresh instance matching its own version, ensuring backward compatibility and preventing API schema mismatches.
How does the build process inject the version into the worker service?
The build pipeline uses esbuild's define feature to replace the global constant __DEFAULT_PACKAGE_VERSION__ with the actual version string from package.json. The script scripts/verify-timestamp-fix.ts orchestrates this injection during the build, ensuring the worker binary contains an immutable version identifier that persists across restarts.
Can I manually check if my plugin and worker versions match?
Yes, you can programmatically verify version alignment using the checkVersionMatch utility exported from src/services/infrastructure/HealthMonitor.ts. This function queries the worker's /api/version endpoint and returns a boolean matches field along with both version strings, allowing you to detect drift before executing operations.
What safeguards exist to prevent infinite restart loops on Windows?
The codebase implements a lock file mechanism specifically for Windows platforms (process.platform === 'win32') that creates a cooldown period between worker restarts. This prevents the rapid respawn loops that would otherwise flood the system with cmd.exe dialog windows if version detection encounters edge cases or race conditions during startup.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →