You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reviewed the Services subprocess integration (Rust core, NAPI/TS, gateway3 typed HTTP). Verified the findings below directly against the diff.
Bugs
1. Services silently never starts for non-Auto-managed engines despite start_services: true (rivetkit-rust/packages/rivetkit-core/src/registry/mod.rs:596-609) DevelopmentProcessManager::start (which owns the Services spawn) is only invoked when manage_engine = should_manage_engine(&config.endpoint, config.engine_spawn)? is true. If start_services is true but should_manage_engine is false (e.g. engine_spawn isn't Auto, or the endpoint isn't loopback), development_processes stays None and ServicesProcessManager::start is never called — with no error or log pointing at the cause. The same gating pattern is duplicated in serverless.rs's CoreServerlessRuntime::new.
2. wait_for_readiness can pass against a stale registration, not the newly spawned child (rivetkit-rust/packages/rivetkit-core/src/services_process.rs:321-360)
The readiness poll queries GET /envoys?namespace=...&name=<pool_name> and treats any non-empty response as ready. It never checks that the returned envoy entry corresponds to the child process just spawned (no PID/envoy-key/generation match). If a prior Services instance for the same namespace/pool is still registered (crash-restart race, slow expiry), a fresh spawn can report ready immediately while the old instance is actually what's serving.
3. MAX_CONCURRENT_LIVE_HTTP_CALLBACK_STARTS defaults to 0, silently blackholing "Live" HTTP callbacks (rivetkit-rust/packages/rivetkit/src/actor.rs:54, wired in start.rs:258-300) MAX_CONCURRENT_HTTP_CALLBACKS defaults to 128, but the sibling MAX_CONCURRENT_LIVE_HTTP_CALLBACK_STARTS defaults to 0, and try_acquire_owned() against a 0-permit semaphore always fails. An actor that sets CONCURRENT_HTTP_CALLBACKS = true and overrides classify_http_request to route requests to HttpCallbackClass::Live, without also overriding the max constant, will have every such request permanently rejected with no compile-time signal.
4. Rust host's start_services default diverges from the TypeScript host's (rivetkit-rust/packages/rivetkit-core/src/registry/envoy_callbacks.rs:169 vs rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts:366-372)
TS defaults startServices to Boolean(endpoint && isLocalEngineEndpoint(endpoint)) — on whenever RivetKit is managing a local engine. The Rust ServeSettings::from_env defaults it to a bare RIVET_RUN_SERVICES == "1" check, i.e. off by default regardless of local-engine management. This also contradicts the BinaryUnavailable error text ("...or set RIVET_RUN_SERVICES=0 to disable Services"), which implies Services is on by default.
Architecture (CLAUDE.md layering)
5. HTTP-callback admission control lives in the thin rivetkit (Rust) wrapper instead of rivetkit-core (rivetkit-rust/packages/rivetkit/src/start.rs:258-320, actor.rs)
Per this repo's layering rule, rivetkit (Rust) should stay a thin typed wrapper with no load-bearing logic, and dispatch/admission logic belongs in rivetkit-core so every host (NAPI/TS, future wasm) benefits. HttpCallbackPools, HttpCallbackAdmission, and the semaphore-based Standard/Live admission gating are new load-bearing policy implemented only in the Rust crate — NAPI/TS-hosted actors get no equivalent protection, and this duplicates the shape of rivetkit-core's existing SQLite-tx admission semaphores rather than sharing it.
Tests
6. New test extends an existing vi.mock violation (rivetkit-typescript/packages/rivetkit/tests/registry-constructor.test.ts:10-12)
Adds vi.mock("@rivet-dev/services", () => ({ getServicesPath: () => "/tmp/rivet-services" })). Root CLAUDE.md explicitly prohibits vi.mock/module-level mocking ("Write tests against real infrastructure"). This follows the file's pre-existing vi.mock("@rivetkit/engine-cli", ...), but compounds rather than fixes the violation — worth reconsidering both while touching this file.
7. New services-binary resolution logic was added to a dead code path (rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts:805-861) runtime.ts has its own buildServeConfig, parallel to the live one in native.ts. Grepping the repo, runtime.ts's copy has zero callers anywhere (only native.ts's buildServeConfig is imported by tests/other code). This PR adds the new startServices/servicesBinaryPath/loadServicesPath handling to both copies. Since nothing exercises the runtime.ts copy, future edits to the live version (error wording, retry/backoff behavior) can silently drift from the dead one — worth deleting the unused copy or wiring it in.
Minor / worth a second look
services_process.rs's wait_for_readiness (flat 60×500ms poll) and shutdown reimplement patterns that already exist with different tuning in the sibling engine-process crate (e.g. exponential backoff there vs. flat retry here). Not necessarily wrong, but a future fix to one poll loop's backoff is easy to miss applying to the other.
rivetkit-typescript/packages/rivetkit/tests/napi-runtime-integration.test.ts (servicesPid()) parses stdout for an info!-level readiness log; if the spawned process doesn't have RUST_LOG/RIVET_LOG_LEVEL set to at least info, the default warn-level filter in rivetkit-napi's init_tracing would drop the line and fail the test independent of whether Services actually started.
Overall the feature is well-structured (dedicated ServicesProcessManager, structured RivetError variants, version/protocol compatibility checks before spawn), but items 1-4 are functional bugs worth fixing before merge, and item 5 is a layering violation per this repo's own conventions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
servicespool and lifecycle.@rivet-dev/serviceswithout bundling native binaries into edge builds.