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

  1. 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.
  2. Add a cleanup that removes tokenCaches rows when a user is deleted.
  3. Investigate why users and tokenCaches diverged (audit deletion code paths).
  4. 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

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.