HeyPuter/puter · error · HttpError

bad_request

bad_request

Error message

No ETag for ${filePath} part ${partNumber}

What it means

AppDriver.read resolves { uid, id } against the store; a null result means no app matched either key. Thrown as 404 not_found after actor resolution but before access checks, so it does not leak whether an app exists that the caller can't read. Both uid and id are accepted lookup keys.

Source

Thrown at src/backend/clients/s3/S3Client.ts:366

                if (bytesRead <= 0) break;

                const body =
                    bytesRead === partBuffer.length
                        ? partBuffer
                        : partBuffer.subarray(0, bytesRead);
                const { ETag } = await client.send(
                    new UploadPartCommand({
                        Bucket: bucket,
                        ContentLength: bytesRead,
                        Key: key,
                        PartNumber: partNumber,
                        UploadId,
                        Body: body,
                    }),
                );

                if (!ETag)
                    throw new HttpError(
                        400,
                        `No ETag for ${filePath} part ${partNumber}`,
                        { legacyCode: 'bad_request' },
                    );
                uploadedParts.push({ ETag, PartNumber: partNumber });

                offset += bytesRead;
                partNumber++;
            }

            await client.send(
                new CompleteMultipartUploadCommand({
                    Bucket: bucket,
                    Key: key,
                    UploadId,
                    MultipartUpload: { Parts: uploadedParts },
                }),
            );

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Verify the uid/id source — confirm it came from a prior create()/list() in the same environment.
  2. Treat 404 as 'not found or deleted' and refresh the client's app list.
  3. Validate uid format (UUID-ish) before calling to fail fast on garbage.
  4. If the app was deleted, offer to re-create rather than silently looping.

Example fix

// before
const app = await puter.apps.get(staleUid);

// after — handle not-found explicitly
let app;
try { app = await puter.apps.get(storedUid); }
catch (e) {
  if (e?.code === 'not_found') { refreshAppList(); return; }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Fail fast on malformed uids before the round trip.
function looksLikeUid(v) { return typeof v === 'string' && /^[A-Za-z0-9_-]{8,}$/.test(v); }
if (!looksLikeUid(storedUid)) { refreshAppList(); return; }

Try / catch

try { return await puter.apps.get(storedUid); }
catch (e) {
  if (e?.code === 'not_found') { refreshAppList(); notify('App not found.'); return null; }
  throw e;
}

Prevention

When it happens

Trigger: Calling puter.apps.get(uid) or get(id) with a value that matches no app row — typo, deleted app, wrong environment (dev uid sent to prod), or a uid copied from a different object type.

Common situations: Stale uid cached client-side after the app was deleted; cross-environment uid (test → prod); user pasted a file uid instead of an app uid; id confusion (numeric id vs uid string).

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/2749f662153baf17. Report an issue: GitHub.