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

  1. Supply a unique name yourself rather than relying on the dedupe retry loop.
  2. Lower concurrency / serialize creates sharing a base name.
  3. On this error, generate a longer/higher-entropy suffix client-side and retry create() without dedupe_name.
  4. 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

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.