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:

  1. Log the mismatch with detailed version information for debugging
  2. Shutdown the outdated worker via httpShutdown(port)
  3. Wait for port release using waitForPortFree with platform-specific timeouts
  4. Clean up PID files to prevent stale process references
  5. 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.json version into the worker via esbuild's define feature (__DEFAULT_PACKAGE_VERSION__), while the plugin reads the same version dynamically from its manifest.
  • Runtime validation occurs through the /api/version endpoint exposed by the worker, which checkVersionMatch in HealthMonitor.ts queries to compare against the plugin's current version.
  • Automatic resolution triggers when ensureWorkerStarted detects 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →