Skip to content

fix(run-engine): stop a '*' concurrency key stranding its whole base queue - #4628

Open
1stvamp wants to merge 1 commit into
mainfrom
fix/ck-wildcard-master-queue-strand
Open

fix(run-engine): stop a '*' concurrency key stranding its whole base queue#4628
1stvamp wants to merge 1 commit into
mainfrom
fix/ck-wildcard-master-queue-strand

Conversation

@1stvamp

@1stvamp 1stvamp commented Aug 15, 2026

Copy link
Copy Markdown
Member

The bug

A concurrency key is an unrestricted client string (ConcurrencyKeySchema is z.union([z.string(), z.number()]).transform(String)), and concurrencyKeySection does no escaping, so * reaches the queue raw. queueKey then renders it as ...:queue:<q>:ck:*, which is byte-identical to the wildcard member the CK scripts keep in the master queue to mean "this base queue has concurrency-key work".

Every CK script ends with the same pair:

-- Rebalance master queue with ck:* member
redis.call('ZADD', masterQueueKey, earliestIdx[2], ckWildcardName)

-- Remove old-format entry from master queue (transition cleanup)
redis.call('ZREM', masterQueueKey, queueName)

ckWildcardName is toCkWildcard(message.queue), and for a *-keyed run that returns the identical string, so the cleanup on the second line deletes what the rebalance on the first line just wrote.

The master queue then has no entry for that base queue, while ckIndex and the variant queues still hold the work. Every concurrency key on the queue stops being dequeued, not just the * one. It is silent, and it only recovers if some later write happens to re-add the member.

Reproduced before the fix:

master queue AFTER normal ck enqueue: ["{org:...}:queue:task/my-task:ck:*"]
master queue AFTER ck='*' enqueue:    []
ckIndex members (work still queued):  [":ck:user-1", ":ck:*"]
dequeued:                             []

Blast radius is bounded to the environment that triggers it, so it is self-inflicted rather than cross-tenant, but a single trigger stalls the queue for everything on it.

The fix

Guard the cleanup so it never removes the wildcard member:

if queueName ~= ckWildcardName then
  redis.call('ZREM', masterQueueKey, queueName)
end

Applied to all 10 CK scripts (4 enqueue, 6 ack/nack/dead-letter). No key-format change and no migration: a queue already stranded in Redis is repaired by its next write.

I considered rejecting * at the API boundary instead and rejected it. Existing Redis state and TaskRun.concurrencyKey rows already hold raw :-bearing and * keys, so changing key construction would orphan in-flight messages and split concurrency accounting mid-deploy. Boundary validation would still be reasonable as belt-and-braces later, but the Lua guard alone fixes it including for state already out there.

Testing

ckWildcardKey.test.ts covers the enqueue, ack and nack paths. All three pass with the guard and all three fail without it, verified by reverting. Full src/run-queue/ suite is green (166 tests).

Note for #4367

The virtual-time branch adds three more CK scripts with the same pattern (enqueueMessageCkVtimeTracked, enqueueMessageWithTtlCkVtimeTracked, nackMessageCkVtimeTracked). They do not exist on main so they are not in this PR; the same guard needs applying there, and I will do that on that branch.

@changeset-bot

changeset-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 5cd8c9a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b3a20756-64ca-46f1-a705-a3c88e8eef44

📥 Commits

Reviewing files that changed from the base of the PR and between 2cb7d5b and 2b2a3c3.

📒 Files selected for processing (1)
  • internal-packages/run-engine/src/run-queue/tests/ckWildcardKey.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal-packages/run-engine/src/run-queue/tests/ckWildcardKey.test.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout. (20)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (9, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (10, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 12)
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 12)
  • GitHub Check: internal / 🧪 Unit Tests: Internal
  • GitHub Check: fk-cascade-guard / fk-cascade-guard
  • GitHub Check: runops-guard / runops-guard
  • GitHub Check: typecheck / typecheck
  • GitHub Check: code-quality / code-quality
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)

Walkthrough

The run queue preserves the master-queue wildcard entry when the concurrency key is *. The guard applies to enqueue, acknowledge, nack, dead-letter, and tracked queue scripts. Redis-backed tests cover enqueue, acknowledge, and retry flows. A changelog entry documents the fix for queue stalls caused by wildcard concurrency keys.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary fix for wildcard concurrency keys stranding the base queue.
Description check ✅ Passed The description clearly explains the bug, fix, affected scripts, testing, and scope, although it does not use every template heading.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ck-wildcard-master-queue-strand

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

coderabbitai[bot]

This comment was marked as resolved.

@1stvamp
1stvamp force-pushed the fix/ck-wildcard-master-queue-strand branch from 2cb7d5b to 2b2a3c3 Compare August 15, 2026 17:58
…queue

A concurrency key is an unrestricted client string, so '*' reaches the queue
unescaped, and queueKey renders it as a variant name byte-identical to the
wildcard member the CK scripts keep in the master queue for that base queue.

Each CK script rebalances the master queue with that wildcard member and then
removes the 'old-format' entry for the variant it just touched. Where the variant
IS the wildcard those two calls name the same member, so the cleanup undid the
rebalance and took the base queue's only master-queue entry with it. Nothing then
pointed at the queue, so every concurrency key on it silently stopped being
dequeued until some later write happened to re-add the member.

Guards the cleanup in all 10 CK scripts (4 enqueue, 6 ack/nack/dead-letter). No
key-format change, so state already in Redis is repaired by the next write rather
than needing a migration.

Tests cover the enqueue, ack and nack paths, and fail without the guard.
@1stvamp
1stvamp marked this pull request as ready for review August 15, 2026 18:16

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@1stvamp
1stvamp force-pushed the fix/ck-wildcard-master-queue-strand branch from 2b2a3c3 to 5cd8c9a Compare August 15, 2026 18:20
1stvamp added a commit that referenced this pull request Aug 15, 2026
Carries the fix from #4628 into the three CK scripts this branch adds, which do
not exist on main and so could not be covered there. A concurrency key of '*'
renders a variant name identical to the wildcard member the master queue uses for
the base queue, and the unguarded transition cleanup then removed the entry the
rebalance had just written, stranding every concurrency key on that queue.

The pre-existing scripts are fixed in #4628; this is the same one-line guard
applied to enqueueMessageCkVtimeTracked, enqueueMessageWithTtlCkVtimeTracked and
nackMessageCkVtimeTracked.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant