{"record":{"id":"41ba29e0dab10f74","repo":"HeyPuter/puter","slug":"user-not-found","errorCode":"user_not_found","errorMessage":"User not found.","messagePattern":"User not found\\.","errorType":"http","errorClass":"HttpError","httpStatus":404,"severity":"error","filePath":"src/backend/controllers/auth/AuthController.ts","lineNumber":1195,"sourceCode":"    // -- Email confirmation ------------------------------------------\n\n    @Post('/send-confirm-email', {\n        subdomain: ['api', ''],\n        requireUserActor: true,\n        allowUnconfirmed: true,\n        rateLimit: {\n            scope: 'send-confirm-email',\n            limit: 10,\n            window: 60 * 60_000,\n            key: 'user',\n        },\n    })\n    async handleSendConfirmEmail(req: Request, res: Response): Promise<void> {\n        const user = await this.stores.user.getById(req.actor!.user.id!, {\n            force: true,\n        });\n        if (!user)\n            throw new HttpError(404, 'User not found.', {\n                legacyCode: 'user_not_found' as never,\n            });\n        if (user.suspended)\n            throw new HttpError(403, 'Account suspended.', {\n                legacyCode: 'account_suspended',\n            });\n        if (!user.email)\n            throw new HttpError(400, 'No email on file.', {\n                legacyCode: 'bad_request',\n            });\n\n        const code = String(crypto.randomInt(100000, 1000000));\n        await this.stores.user.update(user.id, {\n            email_confirm_code: code,\n        });\n\n        if (this.clients.email) {\n            try {","sourceCodeStart":1177,"sourceCodeEnd":1213,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/auth/AuthController.ts#L1177-L1213","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","If reproducible only under load, check for replication lag between the write and read databases.","Confirm the account wasn't concurrently deleted (check admin/delete-user logs)."],"exampleFix":"// before\nawait post('/send-confirm-email');\n\n// after\ntry {\n  await post('/send-confirm-email');\n} catch (e) {\n  if (e.statusCode === 404 && e.legacyCode === 'user_not_found') {\n    clearSession();\n    navigateToLogin();\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"// not a client-input error; pre-check by validating the session is still alive\nconst me = await getMe();\nif (!me) { clearSession(); navigateToLogin(); return; }","typeGuard":null,"tryCatchPattern":"try {\n  await post('/send-confirm-email');\n} catch (e) {\n  if (e.statusCode === 404 && e.legacyCode === 'user_not_found') {\n    clearSession();\n    navigateToLogin();\n  } else throw e;\n}","preventionTips":["Treat 404 user_not_found on authenticated endpoints as an invalid session.","Investigate concurrent deletion or DB read-replica lag if it spikes.","Clear stale tokens after account deletion on the client."],"tags":["auth","email-confirmation","not-found","account","session"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}