@@ -434,22 +434,22 @@ async function createWorkerTask(
434434 `Task "${ task . id } ": an inline limit on a shared queue uses a gate slot, so at most one named limit can be combined with it.`
435435 ) ;
436436 }
437- const anonymousName = ` task/ ${ task . id } ` ;
437+ const anonymousQueueName = anonymousConcurrencyLimitQueueName ( task . id ) ;
438438 await createWorkerQueue (
439439 {
440- name : concurrencyLimitQueueName ( anonymousName ) ,
440+ name : anonymousQueueName ,
441441 concurrencyLimit : concurrency . inline . perKey ?? concurrency . inline . total ?? null ,
442442 combinedConcurrencyLimit : concurrency . inline . total ?? null ,
443443 } ,
444- anonymousName ,
444+ `task/ ${ task . id } ` ,
445445 "NAMED" ,
446446 worker ,
447447 environment ,
448448 prisma ,
449449 "LIMIT" ,
450450 "V2"
451451 ) ;
452- compiledGates = [ { queue : concurrencyLimitQueueName ( anonymousName ) } , ...compiledGates ] ;
452+ compiledGates = [ { queue : anonymousQueueName } , ...compiledGates ] ;
453453 }
454454 }
455455
@@ -614,7 +614,7 @@ function validateWorkerConcurrencyDeclarations(metadata: BackgroundWorkerMetadat
614614 }
615615
616616 for ( const limit of metadata . concurrencyLimits ?? [ ] ) {
617- concurrencyLimitQueueName ( limit . name ) ;
617+ assertValidConcurrencyLimitName ( limit . name ) ;
618618 }
619619
620620 for ( const task of metadata . tasks ) {
@@ -634,11 +634,10 @@ function validateWorkerConcurrencyDeclarations(metadata: BackgroundWorkerMetadat
634634 }
635635
636636 for ( const name of concurrency . limits ?? [ ] ) {
637- concurrencyLimitQueueName ( name ) ;
637+ assertValidConcurrencyLimitName ( name ) ;
638638 }
639639
640640 if ( concurrency . inline && task . queue ?. name ) {
641- concurrencyLimitQueueName ( `task/${ task . id } ` ) ;
642641 if ( ( concurrency . limits ?? [ ] ) . length > 1 ) {
643642 throw new ServiceValidationError (
644643 `Task "${ task . id } ": an inline limit on a shared queue uses a gate slot, so at most one named limit can be combined with it.`
@@ -652,15 +651,39 @@ function validateWorkerConcurrencyDeclarations(metadata: BackgroundWorkerMetadat
652651 * they can never collide with a user's queue names. */
653652const CONCURRENCY_LIMIT_QUEUE_PREFIX = "limit/" ;
654653
655- function concurrencyLimitQueueName ( limitName : string ) : string {
656- const sanitized = sanitizeQueueName ( limitName ) ;
657- const name = `${ CONCURRENCY_LIMIT_QUEUE_PREFIX } ${ sanitized } ` ;
658- if ( sanitized . length === 0 || name . length > 128 ) {
654+ const CONCURRENCY_LIMIT_NAME_MAX_LENGTH = 128 - CONCURRENCY_LIMIT_QUEUE_PREFIX . length ;
655+
656+ /**
657+ * Named limits require a strict charset so distinct declared names can never merge
658+ * onto one row after queue-name sanitization (which strips disallowed characters).
659+ */
660+ function assertValidConcurrencyLimitName ( name : string ) : void {
661+ if ( ! new RegExp ( `^[a-zA-Z0-9_-]{1,${ CONCURRENCY_LIMIT_NAME_MAX_LENGTH } }$` ) . test ( name ) ) {
659662 throw new ServiceValidationError (
660- `Concurrency limit name "${ limitName } " must sanitize to between 1 and ${ 128 - CONCURRENCY_LIMIT_QUEUE_PREFIX . length } characters .`
663+ `Concurrency limit name "${ name } " must be 1- ${ CONCURRENCY_LIMIT_NAME_MAX_LENGTH } characters using only letters, numbers, underscores and hyphens .`
661664 ) ;
662665 }
663- return name ;
666+ }
667+
668+ function concurrencyLimitQueueName ( limitName : string ) : string {
669+ assertValidConcurrencyLimitName ( limitName ) ;
670+ return `${ CONCURRENCY_LIMIT_QUEUE_PREFIX } ${ limitName } ` ;
671+ }
672+
673+ /**
674+ * Row name for a task's anonymous inline limit. Task ids are not charset-restricted,
675+ * so when sanitization would be lossy (or the name would overflow the 128-char queue
676+ * name limit) a hash of the raw id keeps distinct task ids on distinct rows.
677+ */
678+ function anonymousConcurrencyLimitQueueName ( taskId : string ) : string {
679+ const sanitized = sanitizeQueueName ( taskId ) ;
680+ const name = `${ CONCURRENCY_LIMIT_QUEUE_PREFIX } task/${ sanitized } ` ;
681+ if ( sanitized === taskId && name . length <= 128 ) {
682+ return name ;
683+ }
684+ const hash = createHash ( "sha256" ) . update ( taskId ) . digest ( "hex" ) . slice ( 0 , 8 ) ;
685+ const budget = 128 - `${ CONCURRENCY_LIMIT_QUEUE_PREFIX } task/` . length - hash . length - 1 ;
686+ return `${ CONCURRENCY_LIMIT_QUEUE_PREFIX } task/${ sanitized . slice ( 0 , budget ) } -${ hash } ` ;
664687}
665688
666689/** User queue names may not claim the reserved limit/ namespace. */
@@ -675,8 +698,8 @@ function assertNotReservedQueueName(name: string, context: string): void {
675698/**
676699 * Materializes the worker's declared named concurrency limits (plus any names tasks
677700 * reference without declaring, created uncapped) as LIMIT-role TaskQueue rows. A
678- * total-only limit stores the total as its per-key limit too, so it truly caps
679- * keyless runs as well as the keyed group.
701+ * total-only limit stores the total as its per-key limit too, so no single key (or
702+ * the keyless pool) can exceed it even before the group check applies .
680703 */
681704async function createWorkerConcurrencyLimits (
682705 metadata : BackgroundWorkerMetadata ,
0 commit comments