HeyPuter/puter · warning · HttpError
App not found
Error message
App not found
What it means
When create() is given options.dedupe_name and the requested name already exists, the driver appends a random 4-char suffix and retries up to 4 candidates (i starts at 0; throws when i >= 3 after the increment). If every candidate still collides in existsByName, it gives up with 400 app_name_already_in_use 'Failed to dedupe app name'. This is a backstop for pathological collision, not a normal path.
Source
Thrown at extensions/metering.ts:86
req: Request,
res: Response,
): Promise<void> => {
const actor = Context.get('actor');
if (!actor?.user) throw new HttpError(401, 'Authentication required');
let appId = String(req.params.appIdOrName ?? '');
if (!appId) throw new HttpError(400, 'appId parameter is required');
// If not a UUID-shaped app UID, look up by name
if (!appId.startsWith('app-')) {
const appRows = (await clients.db.read(
'SELECT `uid` FROM `apps` WHERE `name` = ? LIMIT 1',
[appId],
)) as Array<{ uid: string }>;
if (appRows.length > 0) {
appId = appRows[0].uid;
} else {
throw new HttpError(404, 'App not found');
}
}
const appUsage =
await services.metering.getActorCurrentMonthAppUsageDetails(
actor,
appId,
);
res.json(appUsage);
};
export const handleMeteringGlobalUsage = async (
_req: Request,
res: Response,
): Promise<void> => {
const globalUsage = await services.metering.getGlobalUsage();
res.json(globalUsage);
};View on GitHub (pinned to 908ec23eda)
Solutions
- Supply a unique name yourself rather than relying on the dedupe retry loop.
- Lower concurrency / serialize creates sharing a base name.
- On this error, generate a longer/higher-entropy suffix client-side and retry create() without dedupe_name.
- Pre-screen candidate names against select() before submitting.
Example fix
// before
await puter.apps.create({ name: 'my-app' }, { dedupe_name: true }); // fails under concurrency
// after — supply a unique name directly
const unique = `my-app-${crypto.randomUUID().slice(0, 8)}`;
await puter.apps.create({ name: unique }); Defensive patterns
Strategy: fallback
Validate before calling
// Generate a unique name client-side instead of relying on the dedupe loop.
const name = `my-app-${crypto.randomUUID().slice(0, 8)}`;
await puter.apps.create({ name }); Try / catch
try { await puter.apps.create({ name: base }, { dedupe_name: true }); }
catch (e) {
if (e?.code === 'app_name_already_in_use') {
await puter.apps.create({ name: `${base}-${crypto.randomUUID().slice(0,8)}` });
} else throw e;
} Prevention
- Do not rely on the dedupe retry loop under high concurrency.
- Supply your own high-entropy suffix for bulk/seed imports.
- Serialize creates that share a base name.
When it happens
Trigger: Many concurrent create() calls with the same base name and dedupe_name:true, so the random suffix space (36^4) collides; or an adversary pre-populating names matching the suffix pattern. Single-threaded calls almost never hit it.
Common situations: Bulk-import / seeding scripts that fan out many creates of the same name; a namespace already saturated with '<name>-XXXX' entries; very high concurrency against a small app namespace.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/3bc101a2190fdc24.
Report an issue: GitHub.