thedotmack/claude-mem · warning

installPluginDependencies: no package.json at

Error message

installPluginDependencies: no package.json at ${targetDir}

What it means

At the end of shutdown, the PID file is removed only when recordedPid matches currentPid; rmSync(pidFilePath, {force:true}) then threw a non-Error value. force already suppresses ENOENT, so realistic Error causes are EPERM/EACCES (ownership changed, container user mismatch, Windows file lock) — but a non-Error throw is warned while the Error twin is only debug-level. Leftover PID files make the next start think a worker is running until the alive check fails.

Solutions

  1. Inspect `ls -l <pidFilePath>` and fix ownership/permissions (chown/chmod) or delete the stale file manually.
  2. Run the supervisor consistently under one user account so the writer and remover match.
  3. On Windows, exclude the state directory from antivirus/indexer scans.
  4. If a directory now occupies the path, remove it and let the supervisor recreate the file.
Defensive patterns

Strategy: try-catch

Validate before calling

import { accessSync, constants, rmSync } from 'fs';

function removePidFileIfWritable(pidFilePath: string): void {
  try {
    accessSync(pidFilePath, constants.W_OK);
  } catch {
    return; // not writable — leave it; the alive-check will reject it next boot
  }
  try { rmSync(pidFilePath, { force: true }); } catch { /* logged upstream */ }
}

Try / catch

try {
  rmSync(pidFilePath, { force: true });
} catch (error: unknown) {
  if (error instanceof Error) {
    logger.debug('SYSTEM', 'Failed to remove PID file', { pidFilePath }, error);
  } else {
    logger.warn('SYSTEM', 'Failed to remove PID file (non-Error)', { pidFilePath, error: String(error) });
  }
}

Prevention

When it happens

Trigger: PID file or its directory owned by root while the supervisor now runs as a normal user; antivirus/indexer holding the file open on Windows; the path replaced by a directory; storage wrappers throwing non-Error values.

Common situations: Running the tool once with sudo then as a user; Docker volume permission mismatches; crashed cleanup reruns; corporate endpoint protection locking files.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/54fb9d420c6722a9. Report an issue: GitHub.

Appendix: source

Thrown at src/npx-cli/install/setup-runtime.ts:428

  let version = getUvVersion();
  if (!version) {
    await new Promise((r) => setTimeout(r, 1000));
    version = getUvVersion();
  }
  if (!version) {
    installerError(ErrorSeverity.WARN_CONTINUE, {
      component: 'uv-version-probe',
      phase: 'setup-runtime',
      cause: new Error(`uv at ${uvPath} did not respond to --version after retry`),
    }, sum);
    return { uvPath, version: 'unknown' };
  }
  return { uvPath, version };
}

export async function installPluginDependencies(targetDir: string, bunPath: string): Promise<void> {
  if (!existsSync(join(targetDir, 'package.json'))) {
    throw new Error(`installPluginDependencies: no package.json at ${targetDir}`);
  }

  const bunCmd = IS_WINDOWS && bunPath.includes(' ') ? `"${bunPath}"` : bunPath;

  // Per CHANGELOG v12.6.1 -> v12.6.2: tree-sitter-swift's nested
  // tree-sitter-cli postinstall downloads a Rust binary and can hang the
  // install. Bun honors trustedDependencies; npm does not. We additionally
  // pass --ignore-scripts as belt-and-suspenders and bound it with a timeout.
  // Async exec (not execSync): a blocked event loop freezes the installer's
  // clack spinner for the duration of the install, which reads as a stall.
  const runBunInstall = (): Promise<void> =>
    new Promise<void>((resolve, reject) => {
      exec(`${bunCmd} install --frozen-lockfile --ignore-scripts`, {
        cwd: targetDir,
        timeout: INSTALL_TIMEOUT_MS,
        maxBuffer: 16 * 1024 * 1024,
        ...(IS_WINDOWS ? { shell: process.env.ComSpec ?? 'cmd.exe' } : {}),
      }, (error, stdout, stderr) =>

View on GitHub (pinned to e2d1df569a)