Skip to content

fix(contracts): decode growing config unions forward-compatibly - #5055

Merged
juliusmarminge merged 1 commit into
mainfrom
t3code/forward-compatible-config-decode
Jul 30, 2026
Merged

fix(contracts): decode growing config unions forward-compatibly#5055
juliusmarminge merged 1 commit into
mainfrom
t3code/forward-compatible-config-decode

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Jul 30, 2026

Copy link
Copy Markdown
Member

What Changed

  • Added ForwardCompatibleArray to contracts: a wire codec for server→client arrays whose element unions grow over time. Decoding drops elements the current build cannot decode instead of failing the whole payload; encoding is the plain array encoding, byte-identical to before.
  • Applied it to the three growing sets in the config payloads: resolved keybindings (ResolvedKeybindingsConfig), server config issues (ServerConfigIssues), and availableEditors.
  • Authoring/upsert inputs (KeybindingRule, ServerUpsertKeybindingInput) stay strict — clients only create bindings for commands they know.

Why

Nightly 0.0.32-nightly.20260730.957 added filePicker.toggle / projectSearch.toggle, and 0.0.31 clients rejected the entire serverGetConfig response during connect (the session's ready gate awaits initialConfig, so a config decode failure means the client never reaches the connected state). The root cause is that clients validate server-owned, growing literal unions as closed sets.

#5018 worked around this with per-connection command-set version negotiation and server-side filtering. That treats the symptom: every future command addition would require a version bump plus a new filter branch on the WS hot path, and the same break class remained for issue kinds, editor ids, and when-node types. This PR fixes the class instead: clients tolerate unknown members by dropping them. This is safe because every dispatch site matches commands by string equality (an unknown command is already an inert no-op), and the settings UI renders any command string.

Old servers are unaffected (encoded output is unchanged). Already-shipped 0.0.31 clients still need a new store build to pick this up — this intentionally replaces the OTA/negotiation hotpatch.

Verification

  • New contracts tests: unknown commands / unknown when-node types / malformed entries dropped, known rules preserved in order, encode round-trip unchanged; unknown issue kinds and editor ids dropped.
  • New client-runtime session test: a mock newer server sends a config containing an unknown command, unknown issue kind, and unknown editor id over the raw wire — the session reaches ready and exposes only the known subset (this is the exact incident scenario).
  • vp test run packages/contracts packages/client-runtime apps/server/src/server.test.ts apps/server/src/keybindings.test.ts packages/shared — all passing (1000+ tests)
  • vp run --filter @t3tools/contracts --filter @t3tools/client-runtime --filter t3 --filter @t3tools/shared --filter web --filter mobile typecheck — clean
  • Targeted lint + format checks for all changed files

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I added regression coverage

🤖 Generated with Claude Code


Note

Medium Risk
Changes wire decoding for core connect-time ServerConfig fields; behavior is intentionally permissive (drop unknowns) but affects every client session bootstrap path.

Overview
Older clients were failing the entire serverGetConfig decode when a newer server sent unknown keybinding commands, issue kinds, or editor IDs—blocking the session ready gate. This PR introduces ForwardCompatibleArray, which filters out array elements the current schema cannot decode instead of rejecting the whole payload; wire encoding stays unchanged.

That codec is applied to ResolvedKeybindingsConfig, ServerConfigIssues, and availableEditors on server→client config paths. Client authoring inputs (KeybindingRule, upsert payloads) remain strict.

Regression tests cover per-element dropping (unknown commands, when node types, malformed entries), encode round-trips, and a client-runtime session test where a mock newer server still reaches ready with only the known subset.

Reviewed by Cursor Bugbot for commit c0b2abc. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Decode growing config unions forward-compatibly by dropping unknown entries

  • Introduces ForwardCompatibleArray in baseSchemas.ts, which decodes arrays by silently dropping elements that fail to decode rather than failing the entire array.
  • Applies ForwardCompatibleArray to ResolvedKeybindingsConfig, ServerConfigIssues, and ServerConfig.availableEditors so that unknown commands, issue kinds, or editor IDs from a newer server are dropped instead of breaking the config decode.
  • Behavioral Change: previously, a single unrecognized entry in these arrays would cause the entire decode to fail; now such entries are silently filtered out.

Macroscope summarized c0b2abc.

Nightly 0.0.32 added two keybinding commands and 0.0.31 clients rejected
the whole serverGetConfig payload during connect, so they never reached
the connected state. The root cause is that clients validate
server-owned, growing literal unions (keybinding commands, config issue
kinds, editor ids) as closed sets, and one unknown member fails the
entire config decode and takes down the connection.

Introduce ForwardCompatibleArray: a wire codec that drops array elements
the current build cannot decode instead of failing the payload, and use
it for resolved keybindings, server config issues, and available
editors. Encoding is unchanged, authoring/upsert inputs stay strict, and
all client dispatch is by string equality so dropped rules are inert.

Servers can now grow these sets without per-client-version projections
on the wire (the approach previously attempted in #5018).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0db506c1-7341-418a-8f72-da17e1e8aa12

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Jul 30, 2026
@macroscopeapp

macroscopeapp Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved c0b2abc

This is a defensive forward-compatibility fix that makes schema decoding more resilient by dropping unrecognized union variants instead of failing entirely. Changes are self-contained to schema definitions with comprehensive test coverage.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge
juliusmarminge merged commit df78cda into main Jul 30, 2026
17 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/forward-compatible-config-decode branch July 30, 2026 23:11
santiago-ramos-02 pushed a commit to santiago-ramos-02/t3code that referenced this pull request Jul 31, 2026
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Jul 31, 2026
## What's Changed
* fix(mobile): show the correct build channel in Android's Threads page header by @PixPMusic in pingdotgg/t3code#4861
* fix(contracts): decode growing config unions forward-compatibly by @juliusmarminge in pingdotgg/t3code#5055


**Full Changelog**: pingdotgg/t3code@v0.0.32-nightly.20260730.960...v0.0.32-nightly.20260731.961

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.32-nightly.20260731.961
stevesarmiento added a commit to stevesarmiento/harness that referenced this pull request Jul 31, 2026
The official mobile app's config decoder predates upstream pingdotgg#5055
(forward-compatible config unions): one unknown keybinding command literal
in the initial config fails the whole payload and the app drops the
connection in a loop (connect -> green -> drop). Our fork adds commands
(git.*, filePicker.toggle, projectSearch.toggle, thread/modelPicker jumps)
that trigger exactly that.

ws.ts now filters keybinding rules to the upstream-v0.0.31-safe command
set (plus script.*.run) for sessions whose client device type is mobile,
in both the initial config snapshot and live keybindings updates. Fork
web/desktop clients keep the full set. Remove the filter once fixed
mobile builds (post-pingdotgg#5055) are the norm.

server: 1881 passed; typecheck 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PauloDaniel1993 added a commit to PauloDaniel1993/t3code that referenced this pull request Jul 31, 2026
Brings in 30 upstream commits since abc409c, including the libghostty-vt
web terminal (pingdotgg#4860), `npx t3 pair` (pingdotgg#4955), project favicon caching (pingdotgg#4767),
and forward-compatible config union decoding (pingdotgg#5055).

Three files needed manual resolution; the rest auto-merged.

- modelOptions.test.ts / AssetAccess.test.ts: both sides added tests and
  imports at the same spot, so both sides are kept.
- ThreadTerminalDrawer: upstream replaced xterm.js with the libghostty-vt
  surface, so the fork's terminal typography feature was reimplemented on
  the new API. Font family and size now flow through GhosttyTerminalSurface
  `font` options and `setFont`, which owns loading, cell remeasurement,
  refit, and re-render, replacing the manual FitAddon dance.

The appearance default font stack ends in `monospace`, which would swallow
the Nerd Font glyph fallbacks the renderer appends to any face it is given,
so the default now defers to the renderer's own stack and only a genuinely
custom face is forwarded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 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