hcengineering/platform · warning
Not found
Error message
Not found
What it means
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.
Source
Thrown at services/backup/backup-api-pod/src/server.ts:314
'content-type': 'application/json',
etag: jsonData.info?.lastTxId ?? ''
})
.end(JSON.stringify(jsonData))
return
}
if (file.endsWith('.hash')) {
// Just serve the file
const fileInfo = await storage.statInfo(file.slice(0, file.length - 5))
if (fileInfo != null) {
res
.status(200)
.set({
headers: { 'content-type': 'text/plain' }
})
.end(fileInfo.etag)
}
res.status(401).end('Not found')
return
}
// Just serve the file
const fileInfo = await storage.statInfo(file)
if (fileInfo != null) {
const responseHeaders = {
'content-type': fileInfo.contentType ?? 'application/octet-stream',
'content-length': fileInfo.size.toString(),
etag: fileInfo.etag,
'last-modified': new Date(fileInfo.lastModified).toUTCString()
}
// Check If-None-Match header
const ifNoneMatch = headers['If-None-Match']
if (ifNoneMatch === fileInfo.etag) {
res.status(304).set(responseHeaders).end()
returnView on GitHub (pinned to 63e28dc964)
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
Example fix
// before
await fetch(`/api/backup/${ws}/${guess}.hash`)
// after
const idx = await (await fetch(`/api/backup/${ws}/index.json`)).json()
await Promise.all(idx.files.filter(f => f.name === target).map(f => fetch(`/api/backup/${ws}/${f.name}.hash`))) Defensive patterns
Strategy: validation
Validate before calling
const index = await (await fetch(`/api/backup/${ws}/index.json`, { headers })).json()
const target = name + '.hash'
if (![...index.files.map((f: any) => f.name), ...index.files.map((f: any) => f.name + '.hash')].includes(target)) {
throw new Error(`${target} not in backup index`)
} Type guard
function fileInIndex(index: { files: { name: string }[] }, file: string): boolean {
return index.files.some((f) => f.name === file || f.name + '.hash' === file)
} Try / catch
const res = await fetch(hashUrl, { headers })
if (res.status === 401 && (await res.text()) === 'Not found') {
console.warn('hash missing: underlying object absent from backup storage')
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- Unable to find ${RUSH_JSON_FILENAME}.
- Blob file not found: ${blobPath} from:${cardPath}
- File not found: ${fileAbsPath}
- Template file not found: ${templatePath}
- platform.status.Unauthorized
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/5c7dc31f22644947.
Report an issue: GitHub.