# How Claude-Mem Handles Version Mismatch Between Plugin and Worker Service

> Claude-Mem automatically detects and resolves version mismatches between its VS Code plugin and worker service by comparing package versions and initiating a restart.

- Repository: [Alex Newman/claude-mem](https://github.com/thedotmack/claude-mem)
- Tags: internals
- Published: 2026-02-16

---

**Claude-Mem automatically detects version mismatches between its VS Code plugin and worker service by comparing the plugin's [`package.json`](https://github.com/thedotmack/claude-mem/blob/main/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`](https://github.com/thedotmack/claude-mem/blob/main/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`](https://github.com/thedotmack/claude-mem/blob/main/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.

```typescript
// 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`](https://github.com/thedotmack/claude-mem/blob/main/scripts/verify-timestamp-fix.ts) reads the semantic version from the root [`package.json`](https://github.com/thedotmack/claude-mem/blob/main/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:

```typescript
// 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:

```typescript
// 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)`

```typescript
// 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:

```typescript
// 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:

```typescript
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:

```typescript
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:

```typescript
// 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`](https://github.com/thedotmack/claude-mem/blob/main/src/services/worker-service.ts) | Core orchestrator; injects version, starts worker, performs mismatch detection & auto-restart | [worker-service.ts](https://github.com/thedotmack/claude-mem/blob/main/src/services/worker-service.ts) |
| [`src/services/infrastructure/HealthMonitor.ts`](https://github.com/thedotmack/claude-mem/blob/main/src/services/infrastructure/HealthMonitor.ts) | Implements `checkVersionMatch`, `waitForHealth`, `httpShutdown` | [HealthMonitor.ts](https://github.com/thedotmack/claude-mem/blob/main/src/services/infrastructure/HealthMonitor.ts) |
| [`src/shared/worker-utils.ts`](https://github.com/thedotmack/claude-mem/blob/main/src/shared/worker-utils.ts) | Provides `getWorkerPort`, `getWorkerHost` used by the start-up logic | [worker-utils.ts](https://github.com/thedotmack/claude-mem/blob/main/src/shared/worker-utils.ts) |
| [`package.json`](https://github.com/thedotmack/claude-mem/blob/main/package.json) (root) | Single source of truth for the semantic version string | [package.json](https://github.com/thedotmack/claude-mem/blob/main/package.json) |
| [`scripts/verify-timestamp-fix.ts`](https://github.com/thedotmack/claude-mem/blob/main/scripts/verify-timestamp-fix.ts) | Reads `package.json.version` and injects it into the worker via esbuild `define` | [verify-timestamp-fix.ts](https://github.com/thedotmack/claude-mem/blob/main/scripts/verify-timestamp-fix.ts) |

## Summary

- **Version mismatch detection** relies on a build-time injection of [`package.json`](https://github.com/thedotmack/claude-mem/blob/main/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`](https://github.com/thedotmack/claude-mem/blob/main/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`](https://github.com/thedotmack/claude-mem/blob/main/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`](https://github.com/thedotmack/claude-mem/blob/main/package.json). The script [`scripts/verify-timestamp-fix.ts`](https://github.com/thedotmack/claude-mem/blob/main/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`](https://github.com/thedotmack/claude-mem/blob/main/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.