lobehub/lobehub · error · TRPCError
FORBIDDEN
FORBIDDEN
Error message
Invalid Request!
What it means
FORBIDDEN TRPC error raised inside the image-generation worker when GenerationBatchModel.findById(generationBatchId) returns null. FORBIDDEN (not NOT_FOUND) is used deliberately — a missing batch is treated as an authorisation failure because batches are user-scoped and should never be addressable by an unauthorised caller. The generic 'Invalid Request!' message avoids leaking whether the batch exists.
Source
Thrown at apps/server/src/routers/async/image.ts:115
log('Starting async image generation: %O', {
generationId,
imageParams: {
cfg: params.cfg,
height: params.height,
steps: params.steps,
width: params.width,
},
model,
prompt: params.prompt,
provider,
taskId,
});
// Check if generationBatch exists before processing
const generationBatch = await generationBatchModel.findById(generationBatchId);
if (!generationBatch) {
log('Generation batch not found: %s, skipping image generation', generationBatchId);
throw new TRPCError({ code: 'FORBIDDEN', message: 'Invalid Request!' });
}
// Billing context is loaded inside the guarded section below so that a
// failure (e.g. a stale model mapping) still marks the task as Error and
// reconciles the precharge handle; the error path falls back to identity
// mapping when resolution itself is what failed.
// requestedModelId is optional on the mapping result, so it must allow
// undefined even though it defaults to the raw model id.
let requestedModelId: string | undefined = model;
let resolvedModelId = model;
let prechargeResult: unknown;
log('Updating task status to Processing: %s', taskId);
await asyncTaskModel.update(taskId, { status: AsyncTaskStatus.Processing });
// Use AbortController to prevent resource leaks
const abortController = new AbortController();
let timeoutId: ReturnType<typeof setTimeout> | null = null;View on GitHub (pinned to 10f24d7ade)
Solutions
- Re-resolve generationBatchId for the current user before retrying; refresh the batches list in the UI.
- Confirm the authenticated user owns the batch — the FORBIDDEN code intentionally hides existence from non-owners.
- If the batch was deleted by retention, create a new batch and dispatch the image task against it.
- Audit the retention/cleanup job so it does not delete batches that still have pending image tasks.
- On the client, treat FORBIDDEN from this route as 'batch no longer available — refresh and retry'.
Example fix
// before
const generationBatch = await generationBatchModel.findById(generationBatchId);
if (!generationBatch) {
log('Generation batch not found: %s, skipping image generation', generationBatchId);
throw new TRPCError({ code: 'FORBIDDEN', message: 'Invalid Request!' });
}
// after — distinguish owner mismatch (FORBIDDEN) from missing (NOT_FOUND) for ops debugging while keeping user-facing message generic
const generationBatch = await generationBatchModel.findById(generationBatchId);
if (!generationBatch) {
log('Generation batch not found: %s, skipping image generation', generationBatchId);
throw new TRPCError({ code: 'FORBIDDEN', message: 'Invalid Request!' });
}
if (generationBatch.userId !== ctx.userId) {
throw new TRPCError({ code: 'FORBIDDEN', message: 'Invalid Request!' });
} Defensive patterns
Strategy: validation
Validate before calling
const batch = await generationBatchModel.findById(generationBatchId);
if (!batch || batch.userId !== ctx.userId) {
// FORBIDDEN is intentional — do not reveal existence to non-owners; refresh the batch list instead
throw new TRPCError({ code: 'FORBIDDEN', message: 'Invalid Request!' });
} Type guard
function isGenerationBatchRow(value: unknown): value is { id: string; userId: string } {
return typeof value === 'object' && value !== null && typeof (value as any).id === 'string' && typeof (value as any).userId === 'string';
} Try / catch
try {
await trpc.async.image.generate.mutate(input);
} catch (e) {
if (e instanceof TRPCError && e.code === 'FORBIDDEN') {
// batch is gone or not owned — refresh the UI list and stop retrying with the same id
refreshBatches();
} else throw e;
} Prevention
- Client-side, treat FORBIDDEN from this route as 'batch no longer available — refresh'.
- Confirm ownership of generationBatchId before queueing image tasks.
- Audit retention/cleanup so it does not delete batches with pending image tasks.
- Re-create the batch and dispatch with the new id rather than reusing a stale one.
When it happens
Trigger: Client dispatches an image-generation task against a generationBatchId that was deleted, belongs to another user, or never existed; retry of a task after the parent batch was garbage-collected; race between batch deletion and the worker polling the queue.
Common situations: Cross-user batch id leak; batch auto-pruned before the image job ran; UI retry button reusing a stale id; integration test that did not seed the batch row.
Related errors
- BAD_REQUEST
- Create image response is empty
- BAD_REQUEST
- Video generation failed: ${result.error}
- FORBIDDEN
AI-assisted analysis of lobehub/lobehub@10f24d7ade (2026-08-12).
Data as JSON: /api/errors/eb8345c1bfa05b5f.
Report an issue: GitHub.