Skip to content

Extract fork-only handlers out of ws.ts and server.ts - #178

Merged
henrychu04 merged 3 commits into
coder-onlyfrom
t3code/extract-coder-handlers
Oct 10, 2026
Merged

henrychu04 merged 3 commits into
coder-onlyfrom
t3code/extract-coder-handlers

Conversation

@henrychu04

@henrychu04 henrychu04 commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Moves the fork's own code out of apps/server/src/ws.ts and apps/server/src/server.ts into sibling files, so both become upstream's file minus the cut surfaces, and upstream edits to the carried code merge cleanly.

Against the merge-base dacd2cb649:

File Before (added/removed) After (added/removed)
ws.ts +1,370 / −2,350 +246 / −1,715
server.ts +320 / −995 +21 / −971

Layout

  • ws.ts keeps upstream's layerWsRpc, indentation, handler order, and helper order. Its handler object is typed by CoderWsRpcGroup minus CODER_WS_METHODS. One Coder: return hook merges in the handlers from coderWs.ts.
  • coderWs.ts holds:
    • the 17 fork-only methods and their helpers;
    • the merge-request link guard;
    • the helper stdio frame check;
    • wrappers that run the thread root check around upstream's own Files list, read, and write handlers, so upstream's edits to those handlers still reach the code that runs.
  • server.ts keeps upstream's declarations, under upstream's names, for the layers the helper uses. It has three in-place Coder: changes: settings also provide the secret store, the VCS status broadcaster takes the demand-only background policy, and the terminal shares its PTY adapter and process runner. One Coder: export block hands these layers to coderServer.ts, so server.ts imports nothing from the fork.
  • coderServer.ts holds the fork-only layers, CoderRuntimeStartup with the T3 tool bridge binding, the StorageCleanup placement, and makeCoderRuntimeLayer. The helper now imports makeCoderRuntimeLayer from it.

Inventory of the old diff

  • (a) Uncarried surfaces: deleted.
    • ws.ts: editor/config discovery, chat-attachment persistence, auth access, client-origin and analytics readers, ACP registry, device, preview, usage, self-update, relay, diagnostics, and the /ws route.
    • server.ts: HTTP, auth, cloud/tunnel, telemetry, usage, preview, and device layers; layerMakeServer; the upstream runtime composition.
  • (b) Fork-only handlers and helpers: moved to coderWs.ts.
  • (c) Fork-only layer wiring: moved to coderServer.ts.
  • (d) In-place changes: kept, each marked Coder:. In ws.ts, most had no marker before.
    • ws.ts: dispatch and launch (no command queue or client analytics; merge-request link check), the streamContext provides, scheduled-task webhook visibility, loadServerConfig and serverGetConfig, provider refresh, settings update, projectsMutate, server config and lifecycle streams, the stdio frame checks, and the failure-context cases.
    • server.ts: the three changes listed under Layout.
  • Two adoptions of upstream, both in ws.ts:
    • terminalAttach now has upstream's catchCause from feat(auth): allow passive terminal observation pingdotgg/t3code#9791, a fix an earlier sync missed.
    • Upstream's span annotations are back on the 8 carried handlers that had them; with no tracer they export nothing. This removed 6 markers (37 → 31): five annotation-only markers and the drift marker.
  • Identity shim: withPullRequestViewer is an identity function, which makes all 22 merge-request handlers match upstream exactly.

Served-method proof

I listed the [WS_METHODS.x] and [ORCHESTRATION_V2_WS_METHODS.x] keys served before (ws.ts) and after (ws.ts + coderWs.ts).

  • Both lists have 125 keys and are byte-identical; that matches the 125 RPCs in CoderWsRpcGroup. The final CoderWsRpcGroup.of call also enforces completeness at typecheck.
  • 113 of 125 handlers are the old code after normalizing whitespace and comments.
  • The 3 Files wrappers, with upstream's handler inlined, reproduce the old code exactly.
  • 6 handlers now match upstream instead: terminalAttach and the five whose only difference was the annotation.
  • The remaining 3 (dispatch, launch, and subscribe-thread), with the restored annotation stripped back out, are identical to the old code.

Every layer expression is the old one renamed, except three. Each differs only in how it reaches shared leaves:

  • The merge-request service lists its independent providers in upstream's order.
  • Text generation gets the provider registry from the outer graph.
  • Git workflow uses upstream's layerGit.

Effect memoizes these leaves, and each service has a single provider in the graph, so the instances are identical. apps/coder-helper/src/bin.test.ts builds the full runtime in a real helper process and serves serverGetConfig and subscribeShell; it passes.

Merge replay

For each past sync, I merged the upstream commit into the pre-sync fork commit twice: once as it was, and once with the new layout rebuilt at that commit. Then I ran scripts/sync-resolve-deleted.mjs and counted conflict blocks.

Sync ws.ts before → after server.ts before → after
dacd2cb649 (latest) 0 → 0 1 → 0
98beed1a22 1 → 0 3 → 2
b6aa268b12 2 → 0 1 → 1

The server.ts conflicts that remain are upstream edits inside regions the fork deletes. docs/internals/sync.md now says to port them into coderServer.ts. Merging the next upstream batch (4f63275ac7), which doesn't touch either file, is conflict-free before and after.

Verification

  • Each of the first two commits on its own: pnpm typecheck:server and pnpm typecheck:helper pass; the focused ws, ws.projects, V1ImportBoundary, and coderEnvironment tests pass.
  • On HEAD: pnpm typecheck:server, typecheck:helper, typecheck:gateway, knip:check, and pnpm build all exit 0.
  • pnpm test:coder on HEAD: exits 0. The first run hit the documented flaky serverSettings watcher timeout; that test passes alone, and the rerun exited 0.
  • Test updates: V1ImportBoundary.test.ts lists coderServer.ts and coderWs.ts as the importers now.
  • Docs: the seams doc's Transport and RPC handlers entry and sync.md's conflict magnets describe the new layout. sync.md now passes vp fmt.

🤖 Generated with Claude Code

henrychu04 and others added 3 commits October 10, 2026 18:28
ws.ts now has upstream's shape: layerWsRpc with upstream's indentation,
handler order, and helper order, minus the uncarried surfaces, with each
remaining difference marked Coder:. The 17 fork-only methods, their
helpers, the merge-request link guard, and the helper stdio frame check
move to coderWs.ts, which ws.ts merges in at one return hook.

The Files list, read, and write handlers in ws.ts are upstream's again;
coderWs.ts wraps them behind the same thread root verification, so
upstream changes to them reach the code that runs. An identity
withPullRequestViewer shim makes the merge-request handlers upstream's.

The served method set is byte-identical, and every handler and helper
is the old code after normalizing whitespace (the Files wrappers with
upstream's handler inlined reproduce the old ones), except two
deliberate adoptions of upstream: terminalAttach fails its stream on a
setup error, as upstream's pingdotgg#9791 does (a fix an earlier sync missed),
and upstream's span annotations are back on the eight carried handlers
that had them; with no tracer they export nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
server.ts is upstream's file minus the uncarried surfaces: it keeps
upstream's declarations, under upstream's names, for the layers the
helper uses, with three Coder: changes in place (settings expose the
secret store, the VCS status broadcaster takes the demand-only
background policy, the terminal shares its PTY adapter and process
runner). One Coder: export block hands them to coderServer.ts, which
holds the fork-only layers, the startup sequence, StorageCleanup
placement, the T3 tool bridge binding, and makeCoderRuntimeLayer. The
helper imports makeCoderRuntimeLayer from coderServer.ts.

No behavior change. Every layer expression is the old one renamed,
except three that differ only in how they reach shared leaves: the
merge-request service lists its independent providers in upstream's
order; text generation takes the provider registry from the outer graph
instead of providing the same memoized layer again; and Git workflow
takes upstream's layerGit, which adds the already built Git driver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Transport and RPC handlers seam now says that ws.ts and server.ts
are upstream's files minus the cut surfaces, names the in-place Coder:
changes they keep, and points the fork-only methods, the Files root
verification, the T3 tool bridge binding, the agent-session scanner,
and StorageCleanup placement at coderWs.ts and coderServer.ts. The sync
checklist's conflict magnets say how to resolve the two files now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@henrychu04
henrychu04 merged commit 82ee024 into coder-only Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant