HeyPuter/puter · error · HttpError

user_not_found

user_not_found

Error message

User not found.

What it means

Thrown by POST /send-confirm-email (HTTP 404, legacyCode 'user_not_found') when the authenticated actor's user row cannot be loaded by id (this.stores.user.getById with force:true bypasses cache and still returns null). The route requires a user actor (requireUserActor:true) and allows unconfirmed actors, so reaching here means the session token resolved to a user id that no longer exists in the DB — a delete/race condition, not a normal client error.

Source

Thrown at src/backend/controllers/auth/AuthController.ts:1195

    // -- Email confirmation ------------------------------------------

    @Post('/send-confirm-email', {
        subdomain: ['api', ''],
        requireUserActor: true,
        allowUnconfirmed: true,
        rateLimit: {
            scope: 'send-confirm-email',
            limit: 10,
            window: 60 * 60_000,
            key: 'user',
        },
    })
    async handleSendConfirmEmail(req: Request, res: Response): Promise<void> {
        const user = await this.stores.user.getById(req.actor!.user.id!, {
            force: true,
        });
        if (!user)
            throw new HttpError(404, 'User not found.', {
                legacyCode: 'user_not_found' as never,
            });
        if (user.suspended)
            throw new HttpError(403, 'Account suspended.', {
                legacyCode: 'account_suspended',
            });
        if (!user.email)
            throw new HttpError(400, 'No email on file.', {
                legacyCode: 'bad_request',
            });

        const code = String(crypto.randomInt(100000, 1000000));
        await this.stores.user.update(user.id, {
            email_confirm_code: code,
        });

        if (this.clients.email) {
            try {

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Treat a 404 user_not_found on an authenticated endpoint as an invalid session: clear local auth state and send the user to login/signup.
  2. If reproducible only under load, check for replication lag between the write and read databases.
  3. Confirm the account wasn't concurrently deleted (check admin/delete-user logs).

Example fix

// before
await post('/send-confirm-email');

// after
try {
  await post('/send-confirm-email');
} catch (e) {
  if (e.statusCode === 404 && e.legacyCode === 'user_not_found') {
    clearSession();
    navigateToLogin();
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// not a client-input error; pre-check by validating the session is still alive
const me = await getMe();
if (!me) { clearSession(); navigateToLogin(); return; }

Try / catch

try {
  await post('/send-confirm-email');
} catch (e) {
  if (e.statusCode === 404 && e.legacyCode === 'user_not_found') {
    clearSession();
    navigateToLogin();
  } else throw e;
}

Prevention

When it happens

Trigger: An authenticated POST /send-confirm-email whose actor.user.id has no matching row — e.g. the account was deleted between session creation and this call, a DB replication lag gap, or a token minted against a since-purged user.

Common situations: A user triggers deletion then a still-open tab fires the confirm-email call; a test/staging environment where the user row was wiped but sessions persisted; a multi-DB setup where the read replica lags behind a delete.

Related errors


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