Public: anyone can read this; the crew can change it.

test/apps-ws.test.ts fails 5 of 8 in the full gate run, passes alone #10986

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

I merged `main` into this branch, kept the intent of both sides in the three conflicting files, and pushed. `bun scripts/gate.ts` and `bun x tsc --noEmit` are clean. The 116 tests I ran (113 passed, 3 skipped, 0 failed) cover `test/apps-ws.test.ts`, `test/check-list.test.ts`, `test/apps.test.ts`, `test/apps-adversarial.test.ts` and `test/hardening.test.ts`. The skipped ones are the unix-socket cas…

shipped production is serving it deploy da127e7

opened this

I merged main into this branch, kept the intent of both sides in the three conflicting files, and pushed. bun scripts/gate.ts and bun x tsc --noEmit are clean. The 116 tests I ran (113 passed, 3 skipped, 0 failed) cover test/apps-ws.test.ts, test/check-list.test.ts, test/apps.test.ts, test/apps-adversarial.test.ts and test/hardening.test.ts. The skipped ones are the unix-socket cases, which this sandbox can't run; the forge's check runs them on the push.

How each conflict was resolved:

  • scripts/gate.ts: Takes main's new shape, where the gate is every test file minus EXCLUDED and the separate GATE list is gone. It drops the test/apps-ws.test.ts exclusion main had added, because this branch fixes that test, so it is gated again.
  • src/apps/proxy.ts: Keeps this branch's appWebSocketWith(max), which lets an app's config set its own frame limit. It adds main's per-port socket hold, which open takes out and close releases.
  • src/http/server.ts: Takes main's reorganised serve, with fetch: handle and serveInternal. The WebSocket handler is now withShells(appWebSocketWith(app.config.wsMaxMessage)), and the imports keep both type AppSocket and isWebSocketHandshake. A stale appWebSocket mention in a comment in src/spawn/cell.ts now says appWebSocketWith.

Try: on the preview, open a WebSocket to an app and send a frame; it should echo back through the proxy.

🤖 Generated with Claude Code

Continues #10986

commented

I merged main into this branch, kept the intent of both sides in the three conflicting files, and pushed. bun scripts/gate.ts and bun x tsc --noEmit are clean. The 116 tests I ran (113 passed, 3 skipped, 0 failed) cover test/apps-ws.test.ts, test/check-list.test.ts, test/apps.test.ts, test/apps-adversarial.test.ts and test/hardening.test.ts. The skipped ones are the unix-socket cases, which this sandbox can't run; the forge's check runs them on the push.

How each conflict was resolved:

  • scripts/gate.ts: Takes main's new shape, where the gate is every test file minus EXCLUDED and the separate GATE list is gone. It drops the test/apps-ws.test.ts exclusion main had added, because this branch fixes that test, so it is gated again.
  • src/apps/proxy.ts: Keeps this branch's appWebSocketWith(max), which lets an app's config set its own frame limit. It adds main's per-port socket hold, which open takes out and close releases.
  • src/http/server.ts: Takes main's reorganised serve, with fetch: handle and serveInternal. The WebSocket handler is now withShells(appWebSocketWith(app.config.wsMaxMessage)), and the imports keep both type AppSocket and isWebSocketHandshake. A stale appWebSocket mention in a comment in src/spawn/cell.ts now says appWebSocketWith.

Try: on the preview, open a WebSocket to an app and send a frame; it should echo back through the proxy.

🤖 Generated with Claude Code

commented

test/apps-ws.test.ts is back in the gate. Its run time drops from about 30 s to about 4 s, and running the tests in random order no longer breaks it.

Why it failed in the gate. There were two causes:

  • The frame-limit test was slow. It sent 16 MiB frames three times. Under load that ran past its 30 s timeout, and the tests after it failed too.
  • The tests depended on their order. "An app that stops closes its sockets" leaves the app stopped. The gate shuffles test order, so any test that ran after it found no app running. With the frame-limit test fixed, this still failed 1–9 of 11 tests per seed.

What changed

  • src/apps/proxy.ts: the websocket handler is now made by appWebSocketWith(maxMessage = WS_MAX_MESSAGE). The same value sets Bun's maxPayloadLength and backpressureLimit and the app-side 1009 check. The production limit is still 16 MiB.
  • src/app.ts / src/http/server.ts: a new optional config field, wsMaxMessage, is passed to appWebSocketWith. Only the test sets it. The shell's own 64 KiB keystroke cap is separate and unchanged.
  • test/apps-ws.test.ts: the forge in this test runs with a 2 MiB limit (LIMIT), and the frame-limit test checks against that. The stop test now redeploys production in a finally and waits for that deploy to finish, so the next test finds the app up.
  • scripts/gate.ts: the file's EXCLUDED entry is removed.
  • src/spawn/cell.ts: a doc comment now names appWebSocketWith.

What to look at. This branch already had an earlier, larger attempt (a rewrite of proxy.ts) from about 200 releases ago. I didn't build on it. I started from today's main and added a merge commit on top, so nothing was force-pushed. Compared with main, the change is 6 files, +41/−24.

Checks

  • bun scripts/flake-hunt.ts admit test/apps-ws.test.ts: 10/10 green, random order, under load (seeds 2081648570, 4153796764, 4118917717, 3274170533, 3876785141, 2488489320, 423412369, 3419342259, 2430598469, 907661019).
  • bun scripts/gate.ts and bun x tsc --noEmit are clean.
  • test/check-list.test.ts and test/mcp-size.test.ts pass. This change doesn't touch the MCP.
  • Because the change touches src/app.ts, scripts/fast.ts named 2,215 test files, about 187 minutes. Instead I picked the tests covering the proxy and the shared websocket handler: apps-ws, apps-proxy-*, machine-shell, machine-shell-page and check-list. All 85 pass. The forge's full check covers the rest.
  • In this sandbox the forge runs apps uncontained (FORGE_TEST_SOCKET=port), so the forge's check on this push runs the contained path.

Left out

  • There is no test that the live 16 MiB value itself is enforced. The test checks the limit at 2 MiB, and production uses the same code with 16 MiB passed in.
  • keep_plan wasn't available to me in this run, so the issue has no plan list.

Try: bun scripts/flake-hunt.ts admit test/apps-ws.test.ts, or bun test --randomize test/apps-ws.test.ts.

🤖 Generated with Claude Code

commented

test/apps-ws.test.ts is back in the gate: its entry is removed from EXCLUDED in scripts/gate.ts.

Why it failed under load. The frame-limit test pushed the real 16 MiB limit through the proxy three times. That took about 30 s, so under load it went past its timeout. Separately, the "app stops" test left the app stopped, so in shuffled order the tests after it failed too.

What changed

  • Frame limit you can set: src/apps/proxy.ts now exports appWebSocketWith(maxMessage = WS_MAX_MESSAGE) in place of the fixed appWebSocket handler. A new wsMaxMessage option in ForgeConfig (src/app.ts) passes the limit through serve in src/http/server.ts. Production still defaults to 16 MiB; only the test lowers it.
  • Smaller test frames: the frame-limit test now runs at 2 MiB. It still covers the same three cases: one byte over from the browser is refused, one byte over from the app closes with 1009 "message too big", and exactly at the limit passes both ways as text and binary.
  • Stop test cleans up: the stop test still checks the close reason and the 502 page. In a finally it now redeploys the app and waits for that run to finish, so the tests after it find the app running.
  • Comment: the doc comment in src/spawn/cell.ts names the renamed handler.

What I chose not to do: the real 16 MiB limit is no longer tested end-to-end. The code path is the same and only the number differs, and moving 16 MiB is what made the test slow.

Checks: today's main is merged in. bun scripts/gate.ts passes, bun x tsc --noEmit is clean, and bun test test/apps-ws.test.ts test/mcp-size.test.ts gives 15 passed, 0 failed. Three more runs of apps-ws with --randomize each passed 11 of 11 in about 5 s. An earlier commit on this branch records 10 of 10 green runs from bun scripts/flake-hunt.ts. In this sandbox the apps run uncontained (FORGE_TEST_SOCKET=port), so the forge's check is the first run inside the full sharded gate under load.

To try it: bun test --randomize test/apps-ws.test.ts.

🤖 Generated with Claude Code

commented

test/apps-ws.test.ts is back in the gate: its line comes out of EXCLUDED in scripts/gate.ts. Nothing new was pushed this run. The branch already holds today's main, and the issue this waited on is closed. The forge's check on this push was still running when I looked, so I have no result from it yet.

Why it failed in the full run. The frame-limit test pushed the real 16 MiB limit through the proxy three times. That took about 30 s alone and longer under load, so it ran out of time. Separately, the "app stops" test left the app stopped, so in the gate's shuffled order the tests after it failed too.

What changed

  • A frame limit tests can lower: src/apps/proxy.ts now has appWebSocketWith(maxMessage) instead of the fixed appWebSocket handler. A new wsMaxMessage option in src/app.ts passes the limit through serve in src/http/server.ts. Production still uses 16 MiB.
  • Smaller test frames: the frame-limit test runs at 2 MiB. It still checks one byte over from the browser, one byte over from the app (closed with 1009 "message too big"), and exactly at the limit in text and binary. A run of the file now takes about 5 s instead of about 30 s.
  • The stop test restarts the app: after its checks it redeploys the app and waits for that to finish, so the tests after it find the app running.
  • Comment: a doc comment in src/spawn/cell.ts names the renamed handler.

What I chose not to do: the real 16 MiB value is no longer tested end to end. The code path is the same and only the number differs, and moving 16 MiB is what made the test slow.

Checks: bun scripts/gate.ts and bun x tsc --noEmit pass. bun test test/apps-ws.test.ts test/mcp-size.test.ts gave 15 passed and none failed. Three more runs of apps-ws in random order each passed 11 of 11. In this sandbox the apps run without their usual containment, so the forge's check is the first run of this file inside the full gate under load.

To try it: bun test --randomize test/apps-ws.test.ts

🤖 Generated with Claude Code

commented

test/apps-ws.test.ts used to fail 5 of its 8 tests. Now all its tests pass, and it is back in the gate.

  • Size limit can be set: apps/proxy.ts now builds its websocket handler with appWebSocketWith(maxMessage). The largest frame it allows still defaults to 16 MiB, and ForgeConfig.wsMaxMessage can lower it. The frame-limit test uses this to run at 2 MiB instead of pushing 16 MiB frames. src/http/server.ts passes the setting through. Production behaviour is unchanged.
  • Test fixes: the stop test now starts the app again before the next test runs. scripts/gate.ts no longer excludes the file.
  • Fix for the failing check: test/security-floor-doors.test.ts was stale, and it fails on main too. #10954 pinned the exact text compress(request, response).then(securityFloor). Then #10975 changed that line in server.ts to .then((r) => securityFloor(resourcePolicy(request, r))). The test now checks that the compressed answer still passes through securityFloor, without requiring that exact text. The security floor itself is still applied on that path.

Checks run here: bun scripts/gate.ts, bun x tsc --noEmit, test/apps-ws.test.ts (11 pass) and test/security-floor-doors.test.ts (7 pass). I did not run the full suite; the forge runs it on the push.

To try it: bun test test/apps-ws.test.ts test/security-floor-doors.test.ts.

🤖 Generated with Claude Code

commented

test/apps-ws.test.ts is back in the gate: its line comes out of EXCLUDED in scripts/gate.ts. It no longer depends on timing or on the order the tests run in. The forge has not yet run its check on this push; the full gate under load is the part not yet tried.

Why it failed in the full run. The frame-limit test sent three 16 MiB frames through the proxy. That took about 30 s on its own and longer under load, so it ran out of time. The "app stops" test also left the app stopped, so in shuffled order the tests after it failed.

What changed

  • Frame limit tests can lower: src/apps/proxy.ts now has appWebSocketWith(maxMessage) instead of the fixed appWebSocket handler. A new wsMaxMessage option in src/app.ts passes it through serve in src/http/server.ts. Production still uses 16 MiB.
  • Smaller test frames: the frame-limit test runs at 2 MiB and still checks one byte over from the browser, one byte over from the app (closed with 1009 "message too big"), and exactly at the limit in text and binary. The file now runs in about 5 s instead of about 30 s.
  • Stop test brings the app back: after its checks it redeploys the app and waits for that to finish, so the next test finds it running.
  • Comment: a doc comment in src/spawn/cell.ts names the renamed handler.
  • Merge with main: both sides had changed the same assertion in test/security-floor-doors.test.ts, and this branch's version is replaced by main's, so that file matches main.

Left out: the real 16 MiB value is no longer tested end to end. The code path is the same, only the number differs, and moving 16 MiB is what made the test slow.

Checks: bun scripts/gate.ts and bun x tsc --noEmit pass. bun test test/apps-ws.test.ts test/security-floor-doors.test.ts test/mcp-size.test.ts test/mirror-credit.test.ts gives 32 passed, 0 failed. In this sandbox apps run uncontained, so this file has only been run alone here, never in the full sharded gate.

To try it: bun test --randomize test/apps-ws.test.ts

🤖 Generated with Claude Code

commented

No push this run: the branch already contains all of today's main and is mergeable. The forge's check on the current head (d6ad24b86) is still queued, so the change has not yet been run inside the full sharded gate under load.

test/apps-ws.test.ts is back in the gate (its line comes out of EXCLUDED in scripts/gate.ts). It no longer depends on timing or test order.

Why it failed in the full run. The frame-limit test sent three 16 MiB frames through the proxy, about 30 s alone and longer under load, so it ran out of time. The "app stops" test also left the app stopped, so in shuffled order the tests after it failed.

What changed

  • Lower limit for tests: src/apps/proxy.ts has appWebSocketWith(maxMessage) instead of the fixed appWebSocket handler. A new wsMaxMessage option in src/app.ts passes the limit through serve in src/http/server.ts. Production still uses 16 MiB.
  • 2 MiB frames: the frame-limit test still checks one byte over from the browser, one byte over from the app (closed with 1009 "message too big"), and exactly at the limit in text and binary. The file runs in about 5 s instead of about 30 s.
  • App brought back: after its checks, the stop test redeploys the app and waits for it to finish, so the next test finds it running.
  • Comment: a doc comment in src/spawn/cell.ts names the renamed handler.
  • Merge: test/security-floor-doors.test.ts is main's version, which fixed the same assertion this branch had.

Left out: the real 16 MiB value is no longer tested end to end. The code path is the same and only the number differs, and moving 16 MiB is what made the test slow.

Checks: last run, on this same head: bun scripts/gate.ts and bun x tsc --noEmit pass. bun test test/apps-ws.test.ts test/security-floor-doors.test.ts test/mcp-size.test.ts test/mirror-credit.test.ts gives 32 passed, 0 failed. In this sandbox apps run uncontained, so the full gate under load has not been tried here.

To try it: bun test --randomize test/apps-ws.test.ts

🤖 Generated with Claude Code

commented

Nothing new to push this run. The branch is the same commit as last run (d6ad24b86) and already has everything on main; no new commits have landed there since. The forge's check on this head is still queued, so the change has not yet been run inside the full sharded gate under load.

test/apps-ws.test.ts is back in the gate: its line comes out of EXCLUDED in scripts/gate.ts. It no longer depends on how long the run takes or on the order the tests run in.

Why it failed in the full run. The frame-limit test sent three 16 MiB frames through the proxy. That took about 30 s alone and longer under load, so it ran out of time. The "app stops" test also left the app stopped, so in shuffled order the tests after it failed.

What changed

  • A limit tests can lower: src/apps/proxy.ts has appWebSocketWith(maxMessage) instead of the fixed appWebSocket handler. A new wsMaxMessage option in src/app.ts passes the limit through serve in src/http/server.ts. Production still uses 16 MiB.
  • 2 MiB frames in the test: it still checks one byte over from the browser, one byte over from the app (closed with 1009 "message too big"), and exactly at the limit in text and binary. The file runs in about 5 s instead of about 30 s.
  • The app comes back: after its checks, the stop test redeploys the app and waits for that to finish, so the next test finds it running.
  • Comment: a doc comment in src/spawn/cell.ts names the renamed handler.
  • Merge: test/security-floor-doors.test.ts takes main's version, which fixed the same assertion this branch had changed.

Left out: the real 16 MiB value is no longer tested end to end. The code path is the same and only the number differs; moving 16 MiB is what made the test slow.

Checks: last run, on this same head: bun scripts/gate.ts and bun x tsc --noEmit pass. bun test test/apps-ws.test.ts test/security-floor-doors.test.ts test/mcp-size.test.ts test/mirror-credit.test.ts gives 32 passed, 0 failed. In this sandbox apps run uncontained, so the full gate under load has not been tried here.

To try it: bun test --randomize test/apps-ws.test.ts

🤖 Generated with Claude Code

Approved

Read every changed line. The websocket frame limit is now a parameter (default still 16 MiB), the same value drives Bun maxPayloadLength, backpressureLimit and the app-side 1009 check, and only tests set wsMaxMessage. No new input, auth, or header surface, so no security findings. The stop test restores the app in a finally, which fixes the order dependence, and the apps-ws gate exclusion is removed, which is what #1073 asks for. Minor, non-blocking: the real 16 MiB value is no longer exercised end to end.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs