linshenkx/prompt-optimizer · error · Error

S3 object not found: ${normalized}

Error message

S3 object not found: ${normalized}

What it means

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.

Source

Thrown at packages/ui/src/utils/remote-backup.ts:1626

      updatedAt: new Date().toISOString(),
      contentType,
    }
  }

  async get(path: string): Promise<ArrayBuffer> {
    const normalized = normalizeObjectPath(path)
    let lastError: unknown

    for (let attempt = 1; attempt <= S3_DOWNLOAD_RETRY_ATTEMPTS; attempt += 1) {
      try {
        const response = await this.client.send(new GetObjectCommand({
          Bucket: this.config.bucket,
          Key: this.keyForPath(normalized),
        }))
        return await s3BodyToArrayBuffer(response.Body)
      } catch (error) {
        if (isS3NotFoundError(error)) {
          throw new Error(`S3 object not found: ${normalized}`, { cause: error })
        }
        lastError = error
        if (!isRetryableS3DownloadError(error) || attempt === S3_DOWNLOAD_RETRY_ATTEMPTS) {
          break
        }
        await sleep(S3_DOWNLOAD_RETRY_BASE_DELAY_MS * attempt)
      }
    }

    throw new Error(`S3 download failed: ${s3ErrorMessage(lastError)}`, { cause: lastError })
  }

  async list(prefix: string): Promise<RemoteObjectEntry[]> {
    const entries: RemoteObjectEntry[] = []
    let continuationToken: string | undefined

    try {
      do {

View on GitHub (pinned to 3e677b1d9f)

Solutions

  1. Confirm the object exists under <prefix>/<path> in the configured bucket
  2. If you changed the prefix setting, restore the previous prefix value so keys resolve again
  3. list() first and match by path before get()

Example fix

// before
const buf = await store.get(entry.path)
// after
const entries = await store.list('')
const match = entries.find(e => e.path === entry.path)
if (!match) throw new Error('entry no longer on remote')
const buf = await store.get(entry.path)
Defensive patterns

Strategy: validation

Validate before calling

const entries = await store.list('')
if (!entries.some(e => e.path === path)) throw new Error('no such remote object')

Try / catch

try { await store.get(path) } catch (e) { if ((e as Error).message.startsWith('S3 object not found')) return null; throw e }

Prevention

When it happens

Trigger: 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.

Common situations: Changing the prefix setting after backups were made; restoring from a wiped bucket; listing from one bucket and downloading from another.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of linshenkx/prompt-optimizer@3e677b1d9f (2026-08-27). Data as JSON: /api/errors/c920f2fde05ebc56. Report an issue: GitHub.