Skip to content

Commit 031f4eb

Browse files
committed
fix(webapp): deploy-time guards for the limit namespace
Validation errors now fail the deploy instead of being swallowed into a silently incomplete worker, user queue names may not claim the reserved limit/ prefix, and a limit whose prefixed row name would exceed the 128-character queue maximum is rejected rather than truncated into the wrong row.
1 parent 7d9aab2 commit 031f4eb

1 file changed

Lines changed: 25 additions & 1 deletion

File tree

apps/webapp/app/v3/services/createBackgroundWorker.server.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,10 @@ async function createWorkerTask(
405405
try {
406406
const concurrency = task.concurrency;
407407

408+
if (task.queue?.name) {
409+
assertNotReservedQueueName(task.queue.name, `Task "${task.id}"`);
410+
}
411+
408412
if (concurrency && typeof task.queue?.concurrencyLimit === "number") {
409413
throw new ServiceValidationError(
410414
`Task "${task.id}" declares both a queue concurrencyLimit and the concurrency option; use concurrency.`
@@ -512,6 +516,9 @@ async function createWorkerTask(
512516
gates: compiledGates.length > 0 ? compiledGates : (task.gates ?? null),
513517
};
514518
} catch (error) {
519+
if (error instanceof ServiceValidationError) {
520+
throw error;
521+
}
515522
if (error instanceof Prisma.PrismaClientKnownRequestError) {
516523
// The error code for unique constraint violation in Prisma is P2002
517524
if (error.code === "P2002") {
@@ -585,6 +592,7 @@ async function createWorkerQueues(
585592
const chunk = metadata.queues.slice(i, i + CHUNK_SIZE);
586593
const queueChunk = await Promise.all(
587594
chunk.map(async (queue) => {
595+
assertNotReservedQueueName(queue.name, `Queue "${queue.name}"`);
588596
return createWorkerQueue(queue, queue.name, "NAMED", worker, environment, prisma);
589597
})
590598
);
@@ -599,7 +607,23 @@ async function createWorkerQueues(
599607
export const CONCURRENCY_LIMIT_QUEUE_PREFIX = "limit/";
600608

601609
export function concurrencyLimitQueueName(limitName: string): string {
602-
return `${CONCURRENCY_LIMIT_QUEUE_PREFIX}${sanitizeQueueName(limitName)}`;
610+
const sanitized = sanitizeQueueName(limitName);
611+
const name = `${CONCURRENCY_LIMIT_QUEUE_PREFIX}${sanitized}`;
612+
if (sanitized.length === 0 || name.length > 128) {
613+
throw new ServiceValidationError(
614+
`Concurrency limit name "${limitName}" must sanitize to between 1 and ${128 - CONCURRENCY_LIMIT_QUEUE_PREFIX.length} characters.`
615+
);
616+
}
617+
return name;
618+
}
619+
620+
/** User queue names may not claim the reserved limit/ namespace. */
621+
function assertNotReservedQueueName(name: string, context: string): void {
622+
if (sanitizeQueueName(name).startsWith(CONCURRENCY_LIMIT_QUEUE_PREFIX)) {
623+
throw new ServiceValidationError(
624+
`${context}: queue names starting with "${CONCURRENCY_LIMIT_QUEUE_PREFIX}" are reserved for concurrency limits.`
625+
);
626+
}
603627
}
604628

605629
/**

0 commit comments

Comments
 (0)