HeyPuter/puter · error · HttpError
bad_request
bad_request
Error message
Failed to batch write all items to DynamoDB
What it means
App names are globally unique. create() checks existsByName(fields.name) and, when a match is found and options.dedupe_name is NOT set, throws 400 app_name_already_in_use with the user-facing message. This is the default path; dedupe_name is opt-in.
Source
Thrown at src/backend/clients/dynamodb/DDBClient.ts:312
if (Object.keys(unprocessedItems).length === 0) {
requestItems = {};
break;
}
requestItems = unprocessedItems as NonNullable<
BatchWriteCommandInput['RequestItems']
>;
if (attempt < MAX_BATCH_WRITE_RETRIES) {
const delayMs = Math.min(
1000,
BATCH_WRITE_RETRY_BASE_MS * 2 ** attempt,
);
await sleep(delayMs);
}
}
if (Object.keys(requestItems).length > 0) {
throw new HttpError(
400,
'Failed to batch write all items to DynamoDB',
{ legacyCode: 'bad_request' },
);
}
}
return {
ConsumedCapacity: Array.from(consumedCapacityByTable.entries()).map(
([TableName, CapacityUnits]) => ({
TableName,
CapacityUnits,
}),
),
};
}
@Span('ddb.del', (table: string) => ({ 'db.table': table }))View on GitHub (pinned to 908ec23eda)
Solutions
- Choose a unique name (include a namespace prefix or random suffix).
- Pass options.dedupe_name:true to let the driver auto-suffix (beware the dedupe backstop above).
- Re-use the existing app via update() instead of create() if it is the same app.
- select() by name first to detect the collision and branch accordingly.
Example fix
// before
await puter.apps.create({ name: 'dashboard' });
// after — branch on existence
const existing = await puter.apps.list({ predicate: ['user-can-edit'] });
const mine = existing.find(a => a.name === 'dashboard');
if (mine) await puter.apps.update(mine.uid, { index_url });
else await puter.apps.create({ name: 'dashboard', index_url }); Defensive patterns
Strategy: validation
Validate before calling
// Check the name is free before create().
const taken = await puter.apps.list({ predicate: ['user-can-edit'] });
const nameExists = taken.some(a => a.name === candidate);
if (nameExists) { /* pick another or update() the existing */ } Try / catch
try { await puter.apps.create({ name }); }
catch (e) {
if (e?.code === 'app_name_already_in_use') { await puter.apps.create({ name }, { dedupe_name: true }); }
else throw e;
} Prevention
- Pick namespaced/unique names at create time.
- Re-use update() for re-imports of the same app instead of create().
- Pre-screen against list() before bulk creates.
When it happens
Trigger: Calling puter.apps.create({ name }) where `name` is already taken by any app in the system (owned by anyone). Most common on retries/re-imports or when picking a generic name.
Common situations: Re-running a setup script that already created the app; choosing a common name like 'admin' or 'dashboard'; an old app row was not deleted before re-creating.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/1fa760c6890e2c29.
Report an issue: GitHub.