langfuse/langfuse · error · TRPCError
INTERNAL_SERVER_ERROR
INTERNAL_SERVER_ERROR
Error message
Organization not found
What it means
resolveBillingService loads the organization from Postgres to decide which billing backend (Stripe vs ClickHouse Billing) serves it; if findUnique returns null it throws INTERNAL_SERVER_ERROR 'Organization not found'. The orgId came from a caller that assumed the org exists, so this signals stale or forged input rather than a billing problem.
Source
Thrown at web/src/ee/features/billing/server/resolveBillingService.ts:49
* never a half-configured CHB flow. Orgs already carrying CHB state cannot be
* served by Stripe and error instead.
*
* This dispatch is also the structural interlock that keeps Stripe checkout
* unreachable for any org holding a `cloudConfig.clickhouse` block: such
* orgs always resolve to the CHB service.
*/
export const resolveBillingService = async (
ctx: OrgAuthedContext,
orgId: string,
): Promise<{
billingProvider: BillingProvider;
service: CloudBillingService;
}> => {
const org = await ctx.prisma.organization.findUnique({
where: { id: orgId },
});
if (!org) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Organization not found",
});
}
const parsedOrg = parseDbOrg(org);
// The cutoff is injected rather than read inside the resolver: the shared
// module is exported from the client-safe barrel, so web passes the value from
// its own validated env schema.
const cutoff = env.LANGFUSE_CLOUD_BILLING_CHB_CUTOFF_DATE;
const billingProvider = getBillingProvider(parsedOrg, { cutoff });
if (billingProvider === "clickhouse") {
// Shared process-wide, so every billing request reuses one Auth0 token
// cache rather than minting a token per request.
const client = getChbApiClient();
if (!client) {
if (parsedOrg.cloudConfig?.clickhouse?.organizationId) {
// Sticky CHB org: Stripe cannot serve it, this is a config errorView on GitHub (pinned to 59d92c7cf3)
Solutions
- Verify the orgId is a real organization id from the same tenant/session before calling billing APIs.
- Handle org-deletion races upstream by invalidating sessions/caching when an org is removed.
- Consider NOT_FOUND semantics: this is arguably a 404 and callers should treat missing-org as expected, not a 500.
Example fix
// before
const svc = await resolveBillingService(ctx, maybeStaleOrgId);
// after
const org = await ctx.prisma.organization.findUnique({ where: { id: orgId } });
if (!org) throw new TRPCError({ code: "NOT_FOUND", message: "Organization not found" });
const svc = await resolveBillingService(ctx, orgId); Defensive patterns
Strategy: validation
Validate before calling
const org = await ctx.prisma.organization.findUnique({
where: { id: orgId, members: { some: { userId: session.user.id } } },
});
if (!org) throw new TRPCError({ code: "NOT_FOUND" }); Try / catch
catch (e) { if (e instanceof TRPCError && e.code === "INTERNAL_SERVER_ERROR" && e.message === "Organization not found") { /* treat as stale org, refresh session */ } throw e; } Prevention
- Derive orgId from the authenticated session rather than client input.
- Invalidate sessions and caches on org deletion.
When it happens
Trigger: Passing an orgId to resolveBillingService (transitively any billing service method) for an organization that was deleted, or a typo'd/mismatched org id (e.g. project id passed as orgId).
Common situations: Org deleted while a user had a billing page open; race between org deletion and in-flight requests; tests using fixture ids that were never created.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
AI-assisted analysis of langfuse/langfuse@59d92c7cf3 (2026-08-27).
Data as JSON: /api/errors/4501f76c5c057c38.
Report an issue: GitHub.