Skip to content

chore(deps): upgrade Effect to stable 4.0.0 - #14563

Closed
juliusmarminge wants to merge 2 commits into
mainfrom
t3code/upgrade-stable-effect-v4
Closed

juliusmarminge wants to merge 2 commits into
mainfrom
t3code/upgrade-stable-effect-v4

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Effect 4.0.0 is out as the first stable v4 release. We were on 4.0.0-rc.115. Between the two, Effect removed the effect/unstable/* export paths and the effect/Encoding module, and changed a few APIs we depend on.

Changes

  • Imports. All 675 files that imported effect/unstable/* now use the flattened effect/<area> paths. httpapi is now http-api, and arbitrary/Arbitrary is now effect/Arbitrary. effect/Encoding is replaced by effect/encoding/{Base64,Base64Url,Hex}.
  • Tracing for the browser trace proxy. HttpRouter.serve now builds its app in a private layer memo map. Because of that, the server-wide TracerDisabledWhen predicate, which was merged into makeRoutesLayer, stopped applying, and the browser OTLP proxy route was traced again. server.test.ts caught this. A new withUntracedRequests helper provides the predicate to the served layer, and both the server and the test use it.
  • Closing scopes. Scope.close now only accepts a closeable scope.
    • SSH tunnels take their entry scope explicitly instead of reading it from the context.
    • The Codex session runtime forks a child of the caller's scope so close can still end it.
  • Smaller API changes:
    • Effect.partition now returns successes first.
    • Stream.scan takes a lazy initial state.
    • SchemaGetter.onSome is replaced by transformEffect, and .compose by SchemaGetter.compose / SchemaTransformation.composeTransformation.
    • Schema.brand requires a single concrete brand key.
    • Effect.orElseSucceed now passes the error, which needed a type annotation on the WSL test stub.
  • Patches, re-ported onto 4.0.0:
    • MCP HTTP DELETE. This is now registered in the HTTP protocol layer against the new internal stateful MCP runtime.
    • RpcClient. The request/ping hooks, the three-missed-pong tolerance and the getSetCookie guard carry over. The ping timeout now uses upstream's SocketReadError reason.
    • @effect/vitest. The patch is still just the vite-plus/test import swap. pnpm rejects catalog: in the @effect/vitest packageExtensions entry once it re-resolves, so vite-plus is pinned there literally.

Known gap: relay

infra/relay still uses alchemy@2.0.0-beta.79, the latest published version. It and its @distilled.cloud/* dependencies import the removed effect/unstable/* paths, so the relay will not typecheck, test or deploy until alchemy publishes beta.80. Alchemy main already supports the 4.0.0 module layout. Its preview tarballs depend on URL-pinned distilled packages, which our blockExoticSubdeps policy rejects, so this PR does not pin them. The follow-up is to bump alchemy once beta.80 is on npm.

.repos/effect-smol will be synced in a separate refs PR, as before.

Verification

  • tsc --noEmit is clean for server, web, desktop, mobile, contracts, shared, client-runtime, ssh, tailscale, effect-acp, effect-codex-app-server, scripts and oxlint-plugin-t3code.
  • Focused tests pass:
    • server.test.ts, including the OTLP untraced test
    • McpHttpServer.test.ts, including MCP session DELETE
    • session.test.ts, including missed-pong tolerance
    • The Codex adapter, the SSH tunnel and replay markers
    • Schema JSON and the call sites that moved to the new encoding modules

🤖 Generated with Claude Code


Devin Review

Effect 4.0.0 drops the `effect/unstable/*` export paths, so every import
moves to its flattened `effect/<area>` path (`httpapi` becomes `http-api`).
`effect/Encoding` is split into `effect/encoding/{Base64,Base64Url,Hex}`.

Behavior changes picked up from rc.116 through 4.0.0:
- `HttpRouter.serve` builds the app in a private memo map, so the server's
  `TracerDisabledWhen` predicate is now provided to the served layer.
- `Scope.close` requires a closeable scope: SSH tunnels take their entry
  scope explicitly and the Codex runtime forks a child of the caller's.
- `Effect.partition` returns successes first; `Stream.scan` takes a lazy
  initial state; `SchemaGetter.onSome`/`.compose` are replaced.
- `Schema.brand` requires a single concrete brand key.

The effect patch is re-ported: MCP DELETE now lives in the HTTP protocol
layer against the new stateful runtime, and the missed-pong tolerance and
RpcClient hooks keep upstream's `SocketReadError` ping-timeout reason.

The relay still pins alchemy 2.0.0-beta.79, which imports the removed
paths; it needs alchemy beta.80 before it loads again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Oct 1, 2026
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.5 KiB 13.5 KiB −1 B (−0.0%) 15.1 KiB ✅
Codex Thread snapshot wire 7.0 KiB 7.1 KiB +15 B (+0.2%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.5 KiB 6.4 KiB −16 B (−0.2%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 56.3 KiB 56.2 KiB −44 B (−0.1%) 66.4 KiB ✅
Codex Live turn messages 10 9 −1 (−10.0%) 21 ✅
Claude Total thread wire 13.5 KiB 13.5 KiB −1 B (−0.0%) 15.1 KiB ✅
Claude Thread snapshot wire 7.1 KiB 7.1 KiB +3 B (+0.0%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.5 KiB 6.4 KiB −4 B (−0.1%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 57.0 KiB 57.0 KiB 0 B (0.0%) 66.4 KiB ✅
Claude Live turn messages 9 9 0 (0.0%) 21 ✅

Baseline: 408ff8a · PR result: 6512a81 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 114.0 KiB
  • Claude decoded thread snapshot: 114.7 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

macroscopeapp Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a 703-file Effect major-version migration that includes runtime tracing, scope-lifecycle, parsing, and authentication-path changes rather than only import renames. An unresolved High-severity finding also identifies unbounded ACP parser buffering that can exhaust server memory.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Effect 4.0.0's ndJsonRpc parser skips lines that are not JSON, so a
malformed agent line no longer terminated the ACP session. Split frames
here and decode each line with the strict jsonRpc codec.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment on lines +91 to +100
const makeStrictNdJsonRpcParser = () => {
const codec = RpcSerialization.jsonRpc().makeUnsafe();
const decoder = new TextDecoder();
let buffer = "";
return {
decode: (bytes: Uint8Array | string): ReadonlyArray<unknown> => {
buffer += typeof bytes === "string" ? bytes : decoder.decode(bytes, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
return lines.flatMap((line) => codec.decode(line));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High src/protocol.ts:91

An agent that never sends \n causes buffer to grow without limit, so the server eventually exhausts its memory instead of terminating the session. Add the 16 MiB incomplete-frame cap used by ndJsonRpc() and throw when it is exceeded.

 const makeStrictNdJsonRpcParser = () => {
+  const MAX_BUFFERED_FRAME_SIZE = 16 * 1024 * 1024;
   const codec = RpcSerialization.jsonRpc().makeUnsafe();
   const decoder = new TextDecoder();
   let buffer = "";
   return {
     decode: (bytes: Uint8Array | string): ReadonlyArray<unknown> => {
       buffer += typeof bytes === "string" ? bytes : decoder.decode(bytes, { stream: true });
+      if (buffer.length > MAX_BUFFERED_FRAME_SIZE) {
+        throw new Error("Maximum incomplete JSON-RPC frame size exceeded");
+      }
       const lines = buffer.split("\n");
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/effect-acp/src/protocol.ts around lines 91-100:

An agent that never sends `\n` causes `buffer` to grow without limit, so the server eventually exhausts its memory instead of terminating the session. Add the 16 MiB incomplete-frame cap used by `ndJsonRpc()` and throw when it is exceeded.

@juliusmarminge

Copy link
Copy Markdown
Member Author

Thanks for working on this. We merged the orchestrator V2 rewrite in #2829, and we are closing this PR as part of that transition.

This PR upgrades Effect across 703 pre-V2 files, modifying 34 files removed by the rewrite and no orchestration-v2 files. The newly merged server must be included in the dependency migration and validation. Rebuild the upgrade on current main.

Sorry for the extra work this creates. If the change is still needed on V2, please rebuild it on current main, verify it there, and open a new PR linking back here. We're closing the current implementation without assuming the underlying request is resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant