thedotmack/claude-mem · info

Shutdown already in progress — ignoring re-entrant shutdown…

Error message

Shutdown already in progress — ignoring re-entrant shutdown request

What it means

runShutdownSequence guards against re-entrancy: if a shutdown is already in flight (flag set via markShuttingDown), a second shutdown request is logged and ignored. This prevents double-exit races, duplicate handoff attempts, and conflicting cleanup.

Solutions

  1. No action needed — the first shutdown continues to completion
  2. Avoid sending duplicate signals; use a single SIGTERM and wait for exit
  3. If shutdown seems stuck, inspect logs for the in-flight shutdown's reason and deadline
Defensive patterns

Strategy: validation

Validate before calling

if (isShuttingDown()) {
  console.log('shutdown already in progress; skipping duplicate request');
  return;
}

Try / catch

// Idempotent shutdown wrapper
async function shutdownOnce(reason) {
  if (shuttingDown) return;
  shuttingDown = true;
  await runShutdownSequence({ reason, ... });
}

Prevention

When it happens

Trigger: shutdown() is invoked twice — e.g. SIGINT delivered twice, SIGTERM arriving during an in-progress restart handoff, or both an explicit restart request and an OS signal triggering shutdown concurrently.

Common situations: User presses Ctrl-C repeatedly while a restart handoff is verifying; container orchestrator sends SIGTERM followed by a second signal; systemd stop overlapping with an in-app restart call.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/26e796a229fc62c6. Report an issue: GitHub.

Appendix: source

Thrown at src/services/worker-shutdown.ts:66

  removePidFile: () => void;
  spawnDaemon: (scriptPath: string, port: number) => number | undefined;
}

export interface ShutdownSequenceOptions {
  reason: WorkerShutdownReason;
  /** Reads the owner's shutdown flag (WorkerService.isShuttingDown). */
  isShuttingDown: () => boolean;
  markShuttingDown: () => void;
  /** Pre-graceful bookkeeping: transcript watcher, heartbeat, sentinel, telemetry flush. */
  beforeGracefulShutdown: () => Promise<void>;
  performGracefulShutdown: () => Promise<void>;
  gracefulDeadlineMs: number;
  restartHandoff: RestartHandoffDeps;
}

export async function runShutdownSequence(options: ShutdownSequenceOptions): Promise<void> {
  if (options.isShuttingDown()) {
    logger.warn('SYSTEM', 'Shutdown already in progress — ignoring re-entrant shutdown request', {
      reason: options.reason,
    });
    return;
  }
  options.markShuttingDown();

  try {
    await options.beforeGracefulShutdown();
  } catch (error: unknown) {
    // Pre-graceful bookkeeping (watcher/heartbeat/sentinel/telemetry flush)
    // failing must not abort the sequence: graceful shutdown and — for
    // restarts — the successor handoff still have to run. Same "proceed on
    // error, never abort the handoff" policy as performGracefulShutdown below.
    logger.error(
      'SYSTEM',
      'Pre-graceful shutdown bookkeeping failed — proceeding',
      { reason: options.reason },
      error instanceof Error ? error : new Error(String(error))

View on GitHub (pinned to d8bc9755e7)