{"record":{"id":"c920f2fde05ebc56","repo":"linshenkx/prompt-optimizer","slug":"s3-object-not-found-normalized","errorCode":null,"errorMessage":"S3 object not found: ${normalized}","messagePattern":"S3 object not found: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ui/src/utils/remote-backup.ts","lineNumber":1626,"sourceCode":"      updatedAt: new Date().toISOString(),\n      contentType,\n    }\n  }\n\n  async get(path: string): Promise<ArrayBuffer> {\n    const normalized = normalizeObjectPath(path)\n    let lastError: unknown\n\n    for (let attempt = 1; attempt <= S3_DOWNLOAD_RETRY_ATTEMPTS; attempt += 1) {\n      try {\n        const response = await this.client.send(new GetObjectCommand({\n          Bucket: this.config.bucket,\n          Key: this.keyForPath(normalized),\n        }))\n        return await s3BodyToArrayBuffer(response.Body)\n      } catch (error) {\n        if (isS3NotFoundError(error)) {\n          throw new Error(`S3 object not found: ${normalized}`, { cause: error })\n        }\n        lastError = error\n        if (!isRetryableS3DownloadError(error) || attempt === S3_DOWNLOAD_RETRY_ATTEMPTS) {\n          break\n        }\n        await sleep(S3_DOWNLOAD_RETRY_BASE_DELAY_MS * attempt)\n      }\n    }\n\n    throw new Error(`S3 download failed: ${s3ErrorMessage(lastError)}`, { cause: lastError })\n  }\n\n  async list(prefix: string): Promise<RemoteObjectEntry[]> {\n    const entries: RemoteObjectEntry[] = []\n    let continuationToken: string | undefined\n\n    try {\n      do {","sourceCodeStart":1608,"sourceCodeEnd":1644,"githubUrl":"https://github.com/linshenkx/prompt-optimizer/blob/3e677b1d9f7e0493c142c175560531e7ae786dce/packages/ui/src/utils/remote-backup.ts#L1608-L1644","documentation":"GetObject retried on transient errors; a clean NoSuchKey/404 from S3 is converted to this explicit not-found error (distinct from the retryable path). The requested backup object does not exist under the computed key.","triggerScenarios":"get(path) where the object was deleted, the prefix config changed (keyForPath maps path under config.prefix), or the backup was written under a different prefix/bucket.","commonSituations":"Changing the prefix setting after backups were made; restoring from a wiped bucket; listing from one bucket and downloading from another.","solutions":["Confirm the object exists under <prefix>/<path> in the configured bucket","If you changed the prefix setting, restore the previous prefix value so keys resolve again","list() first and match by path before get()"],"exampleFix":"// before\nconst buf = await store.get(entry.path)\n// after\nconst entries = await store.list('')\nconst match = entries.find(e => e.path === entry.path)\nif (!match) throw new Error('entry no longer on remote')\nconst buf = await store.get(entry.path)","handlingStrategy":"validation","validationCode":"const entries = await store.list('')\nif (!entries.some(e => e.path === path)) throw new Error('no such remote object')","typeGuard":null,"tryCatchPattern":"try { await store.get(path) } catch (e) { if ((e as Error).message.startsWith('S3 object not found')) return null; throw e }","preventionTips":["Never change the prefix setting and expect old backups to resolve","List-then-get when restoring"],"tags":["s3","not-found","object-key"],"backgroundTag":"resource-not-found","analyzedSha":"3e677b1d9f7e0493c142c175560531e7ae786dce","analyzedAt":"2026-08-27T21:29:16.709Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}