{"record":{"id":"5c7dc31f22644947","repo":"hcengineering/platform","slug":"not-found-5c7dc3","errorCode":null,"errorMessage":"Not found","messagePattern":"Not found","errorType":"http","errorClass":null,"httpStatus":401,"severity":"warning","filePath":"services/backup/backup-api-pod/src/server.ts","lineNumber":314,"sourceCode":"          'content-type': 'application/json',\n          etag: jsonData.info?.lastTxId ?? ''\n        })\n        .end(JSON.stringify(jsonData))\n      return\n    }\n\n    if (file.endsWith('.hash')) {\n      // Just serve the file\n      const fileInfo = await storage.statInfo(file.slice(0, file.length - 5))\n      if (fileInfo != null) {\n        res\n          .status(200)\n          .set({\n            headers: { 'content-type': 'text/plain' }\n          })\n          .end(fileInfo.etag)\n      }\n      res.status(401).end('Not found')\n      return\n    }\n\n    // Just serve the file\n    const fileInfo = await storage.statInfo(file)\n    if (fileInfo != null) {\n      const responseHeaders = {\n        'content-type': fileInfo.contentType ?? 'application/octet-stream',\n        'content-length': fileInfo.size.toString(),\n        etag: fileInfo.etag,\n        'last-modified': new Date(fileInfo.lastModified).toUTCString()\n      }\n\n      // Check If-None-Match header\n      const ifNoneMatch = headers['If-None-Match']\n      if (ifNoneMatch === fileInfo.etag) {\n        res.status(304).set(responseHeaders).end()\n        return","sourceCodeStart":296,"sourceCodeEnd":332,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/backup/backup-api-pod/src/server.ts#L296-L332","documentation":"When the requested file ends with '.hash', the server looks up statInfo of the file without the .hash suffix and returns its etag as text/plain; if statInfo returns null it falls through to 401 with body 'Not found'. Note the misleading 401 status — the file simply does not exist in the workspace's backup storage.","triggerScenarios":"GET /api/backup/<workspace>/<name>.hash where '<name>' (the .hash stripped) has no object in backup storage — e.g. requesting a hash for a file never backed up, an already-pruned snapshot file, or a misspelled filename.","commonSituations":"Scripts that fetch every listed file plus its .hash after a partial backup restore, stale index pages listing files removed from storage, or typos in generated file names.","solutions":["Check the file (without .hash) exists via /api/backup/<workspace>/index.json and use an exact listed name","Re-run the backup if the underlying object was pruned","Correct the filename spelling in your download script","Treat this response as 404-style 'missing object' in client logic despite the 401 status"],"exampleFix":"// before\nawait fetch(`/api/backup/${ws}/${guess}.hash`)\n// after\nconst idx = await (await fetch(`/api/backup/${ws}/index.json`)).json()\nawait Promise.all(idx.files.filter(f => f.name === target).map(f => fetch(`/api/backup/${ws}/${f.name}.hash`)))","handlingStrategy":"validation","validationCode":"const index = await (await fetch(`/api/backup/${ws}/index.json`, { headers })).json()\nconst target = name + '.hash'\nif (![...index.files.map((f: any) => f.name), ...index.files.map((f: any) => f.name + '.hash')].includes(target)) {\n  throw new Error(`${target} not in backup index`)\n}","typeGuard":"function fileInIndex(index: { files: { name: string }[] }, file: string): boolean {\n  return index.files.some((f) => f.name === file || f.name + '.hash' === file)\n}","tryCatchPattern":"const res = await fetch(hashUrl, { headers })\nif (res.status === 401 && (await res.text()) === 'Not found') {\n  console.warn('hash missing: underlying object absent from backup storage')\n}","preventionTips":["Enumerate files via index.json before fetching .hash sidecars","Handle this response as missing-file despite the 401 status","Regenerate backups after storage pruning","Avoid constructing .hash URLs for names not listed in the index"],"tags":["http-401","file-not-found","backup-storage"],"backgroundTag":"file-not-found","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}