Repository navigation
Conversation
A session stream ends only when its client hangs up. Behind a front end that speaks HTTP/1.1 to the container, such as Cloud Run's default, the hang-up never arrives: the request stays open until the platform's timeout (up to an hour), and the loop polls the store for nobody while holding a slot of the instance's request concurrency. Reconnecting clients fill every slot, and the api stops answering. This happened on a Cloud Run deployment: 3 instances x 80 slots filled with orphaned streams. - listen.ts serves HTTP/1.1 and h2c on one port, routed by each connection's first bytes, so a deployment can turn on end-to-end HTTP/2 (where a hang-up arrives as an RST_STREAM) while probes, curl, compose and the SDKs keep HTTP/1.1. - The stream route drops the Transfer-Encoding and Connection headers streamSSE sets: HTTP/2 forbids them, and Node sets both itself over HTTP/1.1. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
jinxi97
force-pushed
the
jinxi97/api-h2c-stream-abort
branch
from
October 26, 2026 22:16
3676ac8 to
82b3f7d
Compare
Collaborator
Author
|
Closing: Funky is a batch engine for deferrable tasks, so we're removing /v1/sessions/:id/stream instead of hardening it (clients poll /entries?after=). Replaced by the stream-removal PR. |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Problem
GET /v1/sessions/:id/streamhas no server-side EOF. Its loop polls the store untilstream.aborted, which needs the hang-up to reach the process. Behind a front end that speaks HTTP/1.1 to the container (Cloud Run's default), it doesn't: the request stays open until the platform's timeout (up to an hour). The loop keeps polling everyFUNKY_STREAM_POLL_MSfor nobody, and holds one of the instance's concurrency slots the whole time.Seen on Cloud Run on 2026-10-26: reconnecting clients filled all 240 request slots (3 instances × 80) with orphaned streams. Every request then hung or got
429 Rate exceeded.until a fresh revision was rolled. Request logs showed stream requests completing after >1000s, though their clients had left after 2s.Fix
apps/api/src/listen.ts: HTTP/1.1 and h2c on one port, routed by each connection's first bytes (the h2c preface →node:http2, anything else →node:http). A deployment can now turn on end-to-end HTTP/2, where a hang-up arrives as anRST_STREAM. Health probes, curl, compose and the SDKs keep HTTP/1.1; nothing changes for a deployment that never sends h2c.Transfer-EncodingandConnectionheadersstreamSSEsets. HTTP/2 forbids them: the first fails the response outright, and the second is dropped with a warning. Node sets both itself over HTTP/1.1, so the wire is unchanged there (checked withcurl -i --http1.1).Tests
apps/api/test/listen.test.tsruns the realbuildAppover real sockets:/health;RST_STREAM) and over HTTP/1.1 (closed connection).The h2c case fails without the header change (
ERR_HTTP2_INVALID_CONNECTION_HEADERS). No@hono/node-serverbump is needed: the 2.0 line cancels a started response body on an h2 reset, and a stream's response has always started (checked on 2.0.8). api: 109/109 tests pass;pnpm typecheckandprettier --checkare clean.Deploying behind Cloud Run
Name the container port
h2c(Terraform:ports { name = "h2c" }) for end-to-end HTTP/2. Cloud Run's HTTP probes stay on HTTP/1, which this serves on the same port. funkyhq/cloud does this for its gateway in funkyhq/cloud#95 and will set it for the api once this lands.🤖 Generated with Claude Code