thedotmack/claude-mem · warning

VACUUM INTO failed, falling back to copyFileSync

Error message

VACUUM INTO failed, falling back to copyFileSync

What it means

The cleanup opens the DB read-only and runs SQLite's `VACUUM INTO '<backup>'` to produce a compact backup. If that statement fails, this warning is logged and the code falls back to raw copyFileSync of the db plus its -wal/-shm sidecars. Common failure causes: unwritable backups dir, insufficient disk space, SQLITE_BUSY from a concurrent writer, or a SQLite runtime older than 3.27 that lacks VACUUM INTO.

Source

Thrown at src/services/infrastructure/CleanupV12_4_3.ts:182

        return;
      }
    }
  }

  const effectiveBackupsDir = path.join(effectiveDataDir, 'backups');
  mkdirSync(effectiveBackupsDir, { recursive: true });
  const ts = new Date().toISOString().replace(/[:.]/g, '-');
  backupPath = path.join(effectiveBackupsDir, `claude-mem-pre-12.4.3-${ts}.db`);

  const backupDb = new Database(dbPath, { readonly: true });
  let vacuumFailed = false;
  try {
    backupDb.run(`VACUUM INTO '${backupPath.replace(/'/g, "''")}'`);
    logger.info('SYSTEM', 'v12.4.3 backup created via VACUUM INTO', { backupPath, dbSize });
  } catch (err: unknown) {
    vacuumFailed = true;
    const vacuumError = err instanceof Error ? err : new Error(String(err));
    logger.warn('SYSTEM', 'VACUUM INTO failed, falling back to copyFileSync', {}, vacuumError);
  }
  backupDb.close();

  if (vacuumFailed) {
    try {
      copyFileSync(dbPath, backupPath);
      const walPath = `${dbPath}-wal`;
      const shmPath = `${dbPath}-shm`;
      if (existsSync(walPath)) copyFileSync(walPath, `${backupPath}-wal`);
      if (existsSync(shmPath)) copyFileSync(shmPath, `${backupPath}-shm`);
      logger.info('SYSTEM', 'v12.4.3 backup created via copyFileSync (incl. -wal/-shm if present)', { backupPath, dbSize });
    } catch (copyErr: unknown) {
      const copyError = copyErr instanceof Error ? copyErr : new Error(String(copyErr));
      logger.error('SYSTEM', 'v12.4.3 backup failed via both VACUUM INTO and copyFileSync; aborting cleanup', {}, copyError);
      return;
    }
  }

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Check the follow-up log line: if 'backup created via copyFileSync' appears, you already have a usable backup and only the compact form failed.
  2. Free disk space so dbSize * 1.2 + 100 MB fits, then re-run to get the VACUUM form.
  3. Stop other workers/clients holding the DB so VACUUM is not blocked (SQLITE_BUSY in the logged error).
  4. Fix permissions on the backups directory; if VACUUM INTO keeps failing on an old SQLite runtime, upgrade the runtime or rely on the copy fallback.
Defensive patterns

Strategy: fallback

Validate before calling

// give VACUUM INTO its best chance: space, permissions, no writers
assertWritableDir(backupsDir);
assertFreeSpace(dataDir, statSync(dbPath).size * 1.2 + 100 * 1024 * 1024);
await stopOtherWorkers(); // release write locks

Try / catch

try {
  backupViaVacuumInto(db, backupPath);
} catch (e) {
  // compact backup failed — copyFileSync of db + -wal/-shm is the accepted equivalent
  copyDbWithSidecars(dbPath, backupPath);
}

Prevention

When it happens

Trigger: backupDb.run(`VACUUM INTO ...`) throws: backups directory missing or read-only, disk full, SQLITE_BUSY because another connection holds a write lock, or the bundled SQLite predating VACUUM INTO support.

Common situations: Running cleanup while another claude-mem worker has the DB open for writing; permission-restricted backups directory; disk nearly full; an older runtime supplying SQLite 3.26 or earlier.

Related errors


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