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
Review: feat(rivetkit-rust): select serverless vs envoy runner mode via RIVETKIT_RUNTIME_MODE
1. serverless_listener.rs duplicates rivetkit_core::serverless_http::serve, which is already available for free
rivetkit-rust/packages/rivetkit/src/serverless_listener.rs (new, 155 lines) reimplements an axum HTTP listener that already exists as rivetkit_core::serverless_http::serve (rivetkit-rust/packages/rivetkit-core/src/serverless_http.rs:77). That function is the established pattern for exactly this use case and is already consumed by container-runner/src/main.rs:441 and rivetkit-typescript/packages/rivetkit-napi/src/registry.rs:503.
Notably, serverless_http is gated behind native-runtime (rivetkit-core/src/lib.rs:19-20), which is a default feature of rivetkit-core, and rivetkit's Cargo.toml depends on rivetkit-core with default features enabled. So serverless_http::serve + ListenerConfig were already reachable from the rivetkit crate at zero cost — the new axum, bytes, and tokio-stream direct dependencies added to rivetkit/Cargo.toml weren't necessary to add.
This also runs against this repo's explicit layering rule for this crate (rivetkit-rust/packages/rivetkit/CLAUDE.md and root CLAUDE.md "RivetKit Layer Architecture"): "rivetkit (Rust) is a thin typed wrapper. If it does more than deserialize, delegate to core, and serialize, the logic should move to rivetkit-core." Rebuilding routing/body-limit/CORS/streaming logic here is exactly the kind of thing that should delegate to serverless_http::serve instead.
Concretely, start_serverless (rivetkit-rust/packages/rivetkit/src/registry.rs:167) could just call:
```rust
rivetkit_core::serverless_http::serve(runtime, ListenerConfig { host: None, port, public_dir: None, application: None }, shutdown).await
```
This would also pick up fixes and behavior for free (see #2 and #3 below) instead of reproducing (and regressing) it.
2. Body-read errors are all misreported as "too long" (413)
serverless_listener.rs:92:
```rust
let body = match to_bytes(body, runtime.max_request_body_bytes()).await {
Ok(body) => body,
Err(_) => return into_response(runtime.incoming_too_long_response()),
};
```
Any failure to read the body (client disconnect mid-upload, malformed chunked encoding, etc.) is folded into "incoming too long" and returns 413. The existing core implementation (serverless_http.rs:214-229) distinguishes this correctly: it checks is_length_limit_error(&error) and only returns the 413 incoming_too_long_response() for that case, falling back to a 400 invalid_request_response("failed to read request body") for any other read failure. The new listener loses that distinction, another point in favor of just calling the core serve() function directly.
3. RuntimeMode::from_env() and listen_port() silently fall back instead of failing fast on invalid input
rivetkit-core/src/registry/mod.rs:256:
```rust
pub fn from_env() -> Self {
match env::var("RIVETKIT_RUNTIME_MODE") {
Ok(value) if value.eq_ignore_ascii_case("serverless") => Self::Serverless,
_ => Self::Envoy,
}
}
```
Any value other than exactly "serverless" (case-insensitive), including typos like "servreless" or an accidentally-set empty string, silently resolves to Envoy mode. The TypeScript counterpart (rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts:59-66) explicitly rejects anything other than "envoy"/"serverless" by throwing. This is exactly the case the root CLAUDE.md "Fail-By-Default Runtime" section calls out: "Avoid silent no-ops for required runtime behavior... throw an explicit error with actionable context instead of returning early." A platform (e.g. Rivet Compute) that sets this env var to select serverless mode would fail silently into the wrong runtime mode if the value is ever malformed, which is a hard bug to diagnose since nothing errors.
Same pattern in serverless_listener.rs:56-60 (listen_port()): an unparseable RIVET_PORT silently falls back to 3000 instead of erroring, while the TS parsePortEnv (rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts:73-84) explicitly throws for a non-integer or out-of-range port.
4. Minor: IPv4-only bind vs. established dual-stack pattern
serverless_listener.rs:22 binds Ipv4Addr::UNSPECIFIED (0.0.0.0) only. The existing analogous "serverless front door" in container-runner/src/main.rs:447 deliberately binds dual-stack (::) with an explicit comment: "the engine's metadata client may connect via localhost/::1." If the same constraint applies to this new Rust Registry::start_serverless() listener, the IPv4-only bind could fail to accept that connection in IPv6-preferring environments. Delegating to serverless_http::serve (which already supports host: Some("::")) sidesteps this too.
5. Test coverage
No tests were added for RuntimeMode::from_env() (valid/invalid/case-insensitive values) or for the new listener's request handling. The TypeScript side has explicit coverage for the equivalent invalid/empty RIVETKIT_RUNTIME_MODE and RIVET_PORT cases (rivetkit-typescript/packages/rivetkit/tests/listener.test.ts:38-48). Given finding #3, at minimum a test asserting current fallback behavior (or, better, the corrected fail-fast behavior) would be valuable.
Summary: the core idea (selecting envoy vs. serverless mode via RIVETKIT_RUNTIME_MODE, mirroring the TypeScript registry) is sound and matches the existing into_serverless_runtime split already documented in rivetkit-core/CLAUDE.md. The main issue is that the HTTP listener was hand-rolled in the rivetkit crate instead of reusing rivetkit_core::serverless_http::serve, which already exists, is already used by two other callers in this repo, and doesn't have the body-error and binding gaps introduced here. I'd recommend replacing serverless_listener.rs with a call into that function, and making the two env var parsers fail fast to match the TypeScript behavior and this repo's fail-by-default convention.
abcxff
deleted the
stack/feat-rivetkit-rust-select-serverless-vs-envoy-runner-mode-via-rivetkit_runtime_mode-rqvuzoyz
branch
September 9, 2026 23:21
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.
No description provided.