Skip to content

fix(queue): carry the target namespace from enqueue through to delivery - #622

Open
anthonyiscoding wants to merge 76 commits into
feat/new-sdk-migrationfrom
fix/queue-namespace-propagation
Open

fix(queue): carry the target namespace from enqueue through to delivery#622
anthonyiscoding wants to merge 76 commits into
feat/new-sdk-migrationfrom
fix/queue-namespace-propagation

Conversation

@anthonyiscoding

Copy link
Copy Markdown
Contributor

Problem

An enqueue addressed to a namespace never reached its handler. smoke-tests/ns_queue_dispatch — a worker in jobs registers task::run, the driver enqueues an invocation addressed to jobs — was silently reporting SKIP: no enqueue provider even with the queue worker running and engine::queue::enqueue registered.

The engine was doing the right thing throughout: it sends the target namespace to the enqueue provider and resolves that provider fine. The worker dropped the namespace in three places.

Fixes

  1. Rejected the field outright. EnqueueInput is #[serde(deny_unknown_fields)] and had no namespace, so every namespaced enqueue failed with unknown field 'namespace', expected one of queue, function_id, data, messageReceiptId, traceparent, baggage, _caller_worker_id. The scenario's SKIP branch matches any error containing "enqueue", which is what disguised this as a missing worker.
  2. Didn't persist it. A namespace that isn't stored can't survive a restart. Added to the builtin adapter's on-disk envelope (#[serde(default)], so jobs written by older workers still deserialize), the RabbitMQ AMQP headers on both publish and consume, the redis envelope, and QueueMessage.
  3. Didn't address it on delivery. Invoker was namespace-blind, so deliveries landed in the worker's own namespace. Added call_in_namespace, defaulting to the existing namespace-blind call_with_timeout so the test and embedded invokers need no plumbing, and overrode it in IiiInvoker to target the namespace via the SDK's TriggerRequest::namespace().

function_available needed no change — engine::functions::info already resolves a bare function_id across namespaces and reports the namespace it found.

Testing

  • Worker suite: 130 tests, 0 failures, including the RabbitMQ e2e tests.
  • ns_queue_dispatch: SKIP → 2/2 passed in python, node, and rust.
  • upgrade_queue_builtin_drain, upgrade_queue_rabbitmq_breaking, upgrade_state_survives: still green.

Notes for review

  • publish_to_function_queue is now 11 positional parameters. It was already over the line at 10 (hence the #[allow(clippy::too_many_arguments)]); I added the parameter rather than widen the diff into a signature refactor, but bundling these into a FunctionJob struct would be a good follow-up.
  • The redis adapter publishes the namespace but has no function-queue consume path to read it back, so redis deliveries are unchanged for now.
  • Separately worth doing in smoke-tests: narrow that SKIP branch to a genuine provider-not-found check, so the next contract break fails loudly instead of skipping.

🤖 Generated with Claude Code

guibeira added 30 commits July 25, 2026 19:46
…support

Bump iii-sdk to the 0.22.0-alpha.2 namespace release and adopt namespace
registration: a --namespace flag (III_NAMESPACE env fallback) is threaded into
InitOptions so the worker can register under a namespace. Absent, it keeps the
engine's default namespace, so existing deploys are unchanged. Verified at
runtime: the worker connects and image_resize::resize resolves in the declared
namespace via engine::functions::list.
…upport

Bump iii-sdk to the 0.22.0-alpha.2 namespace release and pass III_NAMESPACE
through registerWorker so claude::* register under a namespace when one is set
(the SDK also resolves the env; passed explicitly for visibility). Absent, the
engine's default namespace is used. Verified at runtime: all six claude::*
functions resolve in the declared namespace via engine::functions::list.
Bump iii-sdk and iii-helpers to the 0.22.0a2 namespace release and pass
III_NAMESPACE through InitOptions so hermes::* register under a namespace when
one is set (the SDK also resolves the env; passed explicitly for visibility).
Absent, the engine's default namespace is used. Verified at runtime: all seven
hermes::* functions resolve in the declared namespace via engine::functions::list.
Bump iii-sdk to the 0.22.0-alpha.2 namespace release. The SDK made
RegisterTriggerInput.namespace required; pass iii.namespace() so the ACP event
subscriber resolves its target in the worker's namespace (None => engine
default, unchanged). Namespace itself is env-driven (III_NAMESPACE) via the SDK.
Build-verified; runtime registration is driven by an ACP stdio client, so the
functions::list smoke does not apply to this worker.
Bump iii-sdk to 0.22.0-alpha.2 and pass III_NAMESPACE through registerWorker so
opencode::* register under a namespace when one is set (SDK also resolves the
env). Absent, the engine's default namespace is used. Smoke-verified: all seven
opencode::* functions resolve in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2 and pass III_NAMESPACE through registerWorker so
pi::* register under a namespace when one is set (SDK also resolves the env).
Absent, the engine's default namespace is used. Smoke-verified: all nine pi::*
functions resolve in the declared namespace.
…port

Bump iii-sdk to 0.22.0a2 and pass III_NAMESPACE through InitOptions so
scrapling::* register under a namespace when one is set (SDK also resolves the
env). Pin iii-helpers==0.22.0a2 (transitive iii-sdk dep) so uv resolves the
pre-release. Absent a namespace, the engine's default is used. Smoke-verified:
all 19 scrapling::* functions resolve in the declared namespace.
… support

Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Also migrate off the removed new_async_with_bad_request (#1966) to new_async, dropping the custom bad-request mapper. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
…ce support

Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
… support

Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
…pport

Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Also migrate off the removed new_async_with_bad_request (#1966) to new_async, dropping the custom bad-request mapper. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
…space support

Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Smoke-verified: functions register in the declared namespace.
…space support

Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Also migrate off the removed new_async_with_bad_request (#1966) to new_async, dropping the custom bad-request mapper. Build- and connect-verified; full registration needs external creds/deps or a driver.
… support

Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Also migrate off the removed new_async_with_bad_request (#1966) to new_async, dropping the custom bad-request mapper. Smoke-verified: functions register in the declared namespace.
…pace support

Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Also migrate off the removed new_async_with_bad_request (#1966) to new_async, dropping the custom bad-request mapper. Smoke-verified: functions register in the declared namespace.
…ce support

Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Also migrate off the removed new_async_with_bad_request (#1966) to new_async, dropping the custom bad-request mapper. Build- and connect-verified; full registration needs external creds/deps or a driver.
…amespace support

Bump iii-sdk to 0.22.0-alpha.2. Namespace is env-driven (III_NAMESPACE via the SDK); the now-required RegisterTriggerInput.namespace is threaded to the worker's namespace so triggers resolve where functions register. Also migrate off the removed new_async_with_bad_request (#1966) to new_async, dropping the custom bad-request mapper. Build- and connect-verified; full registration needs external creds/deps or a driver.
guibeira and others added 20 commits July 25, 2026 21:29
Non-builtin cross-worker trigger calls (e.g. router::/harness::/approval::/session::/context::/shell::/workflow:: and this worker's own functions) issued a TriggerRequest with no namespace, so a worker running in a namespace called those targets in default and missed them. Route each to the worker's namespace via iii.namespace() (None => engine default). Builtin targets (state::/stream::/queue::/pubsub::/configuration::/cron::/http::/engine::/...) intentionally stay in default.
Non-builtin cross-worker trigger calls (e.g. router::/harness::/approval::/session::/context::/shell::/workflow:: and this worker's own functions) issued a TriggerRequest with no namespace, so a worker running in a namespace called those targets in default and missed them. Route each to the worker's namespace via iii.namespace() (None => engine default). Builtin targets (state::/stream::/queue::/pubsub::/configuration::/cron::/http::/engine::/...) intentionally stay in default.
Non-builtin cross-worker trigger calls (e.g. router::/harness::/approval::/session::/context::/shell::/workflow:: and this worker's own functions) issued a TriggerRequest with no namespace, so a worker running in a namespace called those targets in default and missed them. Route each to the worker's namespace via iii.namespace() (None => engine default). Builtin targets (state::/stream::/queue::/pubsub::/configuration::/cron::/http::/engine::/...) intentionally stay in default.
Non-builtin cross-worker trigger calls (e.g. router::/harness::/approval::/session::/context::/shell::/workflow:: and this worker's own functions) issued a TriggerRequest with no namespace, so a worker running in a namespace called those targets in default and missed them. Route each to the worker's namespace via iii.namespace() (None => engine default). Builtin targets (state::/stream::/queue::/pubsub::/configuration::/cron::/http::/engine::/...) intentionally stay in default.
Dynamic subscriber/hook/notify targets carry a runtime function_id that can be either a builtin (e.g. state::set) or another worker's function. Route these to the worker's namespace ONLY when the target is NOT a builtin prefix; builtins keep default routing so a builtin subscribed to an event still resolves. Literal cross-worker targets are unchanged.
Dynamic subscriber/hook/notify targets carry a runtime function_id that can be either a builtin (e.g. state::set) or another worker's function. Route these to the worker's namespace ONLY when the target is NOT a builtin prefix; builtins keep default routing so a builtin subscribed to an event still resolves. Literal cross-worker targets are unchanged.
Dynamic subscriber/hook/notify targets carry a runtime function_id that can be either a builtin (e.g. state::set) or another worker's function. Route these to the worker's namespace ONLY when the target is NOT a builtin prefix; builtins keep default routing so a builtin subscribed to an event still resolves. Literal cross-worker targets are unchanged.
Dynamic subscriber/hook/notify targets carry a runtime function_id that can be either a builtin (e.g. state::set) or another worker's function. Route these to the worker's namespace ONLY when the target is NOT a builtin prefix; builtins keep default routing so a builtin subscribed to an event still resolves. Literal cross-worker targets are unchanged.
Dynamic subscriber/hook/notify targets carry a runtime function_id that can be either a builtin (e.g. state::set) or another worker's function. Route these to the worker's namespace ONLY when the target is NOT a builtin prefix; builtins keep default routing so a builtin subscribed to an event still resolves. Literal cross-worker targets are unchanged.
…heck

Dynamic subscriber/hook/notify targets carry a runtime function_id that can be either a builtin (e.g. state::set) or another worker's function. Route these to the worker's namespace ONLY when the target is NOT a builtin prefix; builtins keep default routing so a builtin subscribed to an event still resolves. Literal cross-worker targets are unchanged.
…heck

Dynamic subscriber/hook/notify targets carry a runtime function_id that can be either a builtin (e.g. state::set) or another worker's function. Route these to the worker's namespace ONLY when the target is NOT a builtin prefix; builtins keep default routing so a builtin subscribed to an event still resolves. Literal cross-worker targets are unchanged.
Picks up the namespace review fixes shipped in the 0.22.0-alpha.3 SDK release. Build-verified against the published crates.
Picks up the namespace review fixes shipped in the 0.22.0-alpha.3 SDK release. Build-verified against the published crates.
Picks up the namespace review fixes in the 0.22.0-alpha.3 SDK release.
Picks up the namespace review fixes in the 0.22.0-alpha.3 SDK release.
Picks up the namespace review fixes in the 0.22.0-alpha.3 SDK release.
An enqueue addressed to a namespace failed outright and, once accepted,
was delivered to the wrong namespace. Three separate drops:

- `EnqueueInput` is `deny_unknown_fields` and had no `namespace`, so the
  namespace the engine sends made every namespaced enqueue fail with
  `unknown field 'namespace'`.
- The job did not persist the namespace, so it could not survive a
  restart: added to the builtin on-disk envelope (`serde(default)`, so
  jobs written by older workers still deserialize), the RabbitMQ headers
  on publish and consume, the redis envelope, and `QueueMessage`.
- `Invoker` was namespace-blind, so deliveries always landed in the
  worker's own namespace. Added `call_in_namespace`, defaulting to the
  existing namespace-blind call so test and embedded invokers are
  unaffected, and overrode it in `IiiInvoker` to address the target via
  the SDK's `TriggerRequest::namespace()`.

`function_available` needs no change: `engine::functions::info` already
resolves a bare `function_id` across namespaces.

Verified with the full worker suite (130 tests, including the RabbitMQ
e2e tests) and the smoke-tests `ns_queue_dispatch` scenario, which went
from SKIP to 2/2 in python, node, and rust; `upgrade_queue_builtin_drain`
and `upgrade_queue_rabbitmq_breaking` stay green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 29, 2026 3:08pm
workers-tech-spec Ready Ready Preview, Comment Jul 29, 2026 3:08pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5cbe2f53-e9eb-4aa7-a5ba-a08a5d14fa69

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 49 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

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.

2 participants