{"record":{"id":"afba8928a0aa7ce9","repo":"overleaf/overleaf","slug":"project-id-and-history-id-are-the-same-for-proje","errorCode":null,"errorMessage":"Project ID and history ID are the same for ${projectId} - use --force to delete anyway","messagePattern":"Project ID and history ID are the same for (.+?) - use --force to delete anyway","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/history-v1/storage/scripts/remove_backup_blobs_from_wrong_path.mjs","lineNumber":163,"sourceCode":"\n/**\n *\n * @param {string} projectId\n * @param {string} hash\n * @return {Promise<void>}\n */\nasync function canDeleteBlob(projectId, hash) {\n  let historyId\n  try {\n    historyId = await getHistoryId(projectId)\n  } catch (error) {\n    if (args.verbose) {\n      console.error(error)\n    }\n    throw new Error(`No history ID found for project ${projectId}, skipping`)\n  }\n  if (historyId === projectId) {\n    throw new Error(\n      `Project ID and history ID are the same for ${projectId} - use --force to delete anyway`\n    )\n  }\n\n  // TODO: fix assert.postgresId to handle integers better and then stop coercing to string below\n  assert.postgresId(\n    `${historyId}`,\n    `History ID ${historyId} does not appear to be for a postgres project`\n  )\n\n  try {\n    await verifyBlobs(`${historyId}`, [hash])\n  } catch (error) {\n    if (args.verbose) {\n      console.error(error)\n    }\n    throw new Error(\n      `Blob ${hash} is not backed up for project ${projectId} - use --force to delete anyway`","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/history-v1/storage/scripts/remove_backup_blobs_from_wrong_path.mjs#L145-L181","documentation":"canDeleteBlob refuses to delete backup blobs when the resolved history id equals the project id. This equality means the blobs are stored under the canonical (correct) path, so deleting them would remove live data, not misplaced backups. The operator must explicitly pass --force to acknowledge the deletion.","triggerScenarios":"The script computes historyId via getHistoryId(projectId) and it is identical to projectId, then canDeleteBlob is invoked without --force.","commonSituations":"Legacy projects whose history id was always the project id; running the script against a project that was never migrated to the new id scheme; forgetting the --force flag the script's docs recommend for such cases.","solutions":["Verify with getHistoryId that this project legitimately has historyId === projectId (legacy project).","Re-run with --force if you are certain the blobs should be deleted.","Exclude such projects from the input list if they are not the target of the cleanup."],"exampleFix":"// before\nnode remove_backup_blobs_from_wrong_path.mjs --project-id 60f0a1...\n// after\nnode remove_backup_blobs_from_wrong_path.mjs --project-id 60f0a1... --force","handlingStrategy":"validation","validationCode":"if (historyId === projectId && !args.force) {\n  console.error(`Refusing: historyId === projectId for ${projectId}; pass --force`)\n  process.exit(1)\n}","typeGuard":null,"tryCatchPattern":"try {\n  await maybeDeleteBlob(projectId, hash)\n} catch (err) {\n  if (/use --force to delete anyway/.test(err.message)) {\n    console.warn(`Needs --force: ${err.message}`)\n  } else throw err\n}","preventionTips":["Understand legacy projects may have historyId === projectId.","Always dry-run (no --commit) first.","Only pass --force after verifying the target is genuinely misplaced backup data."],"tags":["safety-guard","history-id","maintenance-script","cli-flag"],"backgroundTag":"requires-force-flag","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}