ruvnet/ruflo · error · Error
User not found
Error message
User not found
What it means
Thrown in the API-token auth path when a token-hash cache hit is found (tokenCaches collection) but the referenced user no longer exists in the users collection (lookup by hfUserId: cacheHit.userId). It indicates a stale cache entry pointing at a deleted/missing user account.
Source
Thrown at ruflo/src/ruvocal/src/lib/server/auth.ts:467
token: result.oauth?.token?.value,
sessionId,
secretSessionId,
isAdmin: result.user?.isAdmin || adminTokenManager.isAdmin(sessionId),
};
}
if (isApi) {
const authorization = headers.get("Authorization");
if (authorization?.startsWith("Bearer ")) {
const token = authorization.slice(7);
const hash = await sha256(token);
sessionId = secretSessionId = hash;
const cacheHit = await collections.tokenCaches.findOne({ tokenHash: hash });
if (cacheHit) {
const user = await collections.users.findOne({ hfUserId: cacheHit.userId });
if (!user) {
throw new Error("User not found");
}
return {
user,
sessionId,
token,
secretSessionId,
isAdmin: user.isAdmin || adminTokenManager.isAdmin(sessionId),
};
}
const response = await fetch("https://huggingface.co/api/whoami-v2", {
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) {
throw new Error("Unauthorized");
}
View on GitHub (pinned to 6b01dc5a68)
Solutions
- Delete the stale tokenCaches entry (by tokenHash) so the next auth call falls through to the HF whoami path and re-creates or properly rejects it.
- Add a cleanup that removes tokenCaches rows when a user is deleted.
- Investigate why users and tokenCaches diverged (audit deletion code paths).
- Re-authenticate with the token so the cache is repopulated correctly after the user is restored.
Example fix
// before — user gone but cache hit remains → throw
// after — on 'User not found' from a cache hit, evict and retry once
try { await auth(headers); }
catch (e) {
if (e.message === 'User not found') {
await collections.tokenCaches.deleteOne({ tokenHash: hash });
// surface a clean 401 so the client re-authenticates
}
throw e;
} Defensive patterns
Strategy: fallback
Validate before calling
// pre-flight: confirm the cached user still exists before trusting the cache
const cacheHit = await collections.tokenCaches.findOne({ tokenHash: hash });
if (cacheHit) {
const user = await collections.users.findOne({ hfUserId: cacheHit.userId });
if (!user) { await collections.tokenCaches.deleteOne({ tokenHash: hash }); /* fall through to whoami */ }
} Type guard
async function cacheRefersToExistingUser(cacheHit: { userId: string }): Promise<boolean> { return !!(await collections.users.findOne({ hfUserId: cacheHit.userId })); } Try / catch
try { return await authApi(headers); } catch (e) { if ((e as Error).message === 'User not found') { await collections.tokenCaches.deleteOne({ tokenHash: await sha256(token) }); } throw e; } Prevention
- Delete tokenCaches rows when a user is deleted.
- Treat a cache-vs-users mismatch as an eviction, then re-authenticate.
- Audit deletion code paths so users and tokenCaches stay consistent.
When it happens
Trigger: A client sends a Bearer token whose hash is cached, but the user record behind cacheHit.userId was deleted from the users collection after the cache was written. Reached only when isApi is true and the Authorization: Bearer header's token hash matches a tokenCaches document.
Common situations: User account was deleted (GDPR/retention) but tokenCaches was not cleaned up; a DB restore/migration dropped users but kept tokenCaches; multi-instance setup where the cache and users collections diverged; test fixture inserted a tokenCache with a fabricated userId.
Related errors
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/b77c5636d7439ff1.
Report an issue: GitHub.