Cross-platform headers
How web and Electron identify Linux, macOS, and Windows requests with X-Client-Platform and X-App-Version, including the Linux cloud rollout boundary.
Requests made by apps/web carry two client-identification headers:
| Header | Browser value | Electron value | Why |
|---|---|---|---|
X-Client-Platform | web | linux-client, macos-client, or windows-client | Selects the client platform for routing and telemetry |
X-App-Version | 2.0.0 | The packaged desktop application version | Lets cloud gates target specific client versions |
The browser defaults live in
packages/api-client/src/platform-constants.ts:
export const APP_VERSION_HEADER_VALUE = '2.0.0'
export const CLIENT_PLATFORM_HEADER_VALUE = 'web'Inside the Electron app shell, the validated document-start runtime descriptor overrides those defaults. The mapping is explicit:
| Bridge platform | API header | Device registration identity |
|---|---|---|
linux | linux-client | linux / linux-connector / linux |
macos | macos-client | macos / macos-connector / darwin |
windows | windows-client | windows / windows-connector / win32 |
linux-connector is a cloud device clientType; it is not an
X-Client-Platform value. The web/API header remains linux-client.
Why the contract is symmetric
Web, native mobile clients, and the desktop app all use the same two headers with platform-specific values. Cloud services can use them for:
- Auth-context platform routing — downstream services receive a normalized first-party platform when the API middleware recognizes it.
- Version-gated features — routes behind a minimum-client-version gate
compare
X-App-Versionwith the configured minimum. - Telemetry segmentation — platform and version are explicit rather than inferred from user-agent strings.
New browser routes inherit web; a new platform identifier still requires a
coordinated cloud rollout.
Where the headers are injected
The shared browser-side context lives in:
| File | What it does |
|---|---|
apps/web/src/lib/client-runtime.ts | Resolves web vs Electron platform, app version, and device ID |
apps/web/src/lib/client-context-headers.ts | Applies the shared browser/API request context |
apps/web/src/hooks/use-api-client.ts | Configures the generated API client |
apps/web/src/hooks/use-session.ts | Applies the same context to session/token requests |
apps/web/src/lib/chat/protocol/agent-client.ts | Applies the runtime values to agent messages |
Electron also sets the authoritative platform/version headers in the main
process for trusted Today requests and uses the same values for device
registration, tool synchronization, and relay connections. This prevents page
code from downgrading a Linux package to the generic web identity.
The normal browser path is:
const headers = new Headers()
applyClientContextHeaders(headers)
// Browser:
// X-Client-Platform: web
// X-App-Version: 2.0.0
// Linux Electron:
// X-Client-Platform: linux-client
// X-App-Version: <packaged application version>Linux cloud rollout boundary
The client-side Linux contract is implemented, but it does not imply production cloud enablement:
- The current API platform normalizer recognizes
ios,android,web,web-client,macos,windows, andweb-bff. Suffix-bearing values such asmacos-clientandwindows-clientnormalize through their base platform.linux-clientcurrently has no recognizedlinuxbase, so the API omits the platform from downstream auth context. It does not reject an otherwise valid bearer token solely because of that unknown platform value. - The cloud device registry has
linuxandlinux-connectorschemas, but accepts that pair only in thedevenvironment whenLINUX_DEVICE_REGISTRATION_ENABLEDis enabled. The production flag remains disabled.
Before production Linux remote tools are enabled, cloud must add Linux to API platform normalization and deliberately open the device-registration gate. Until then, Linux packages and the local Electron bridge can be built and tested, while production platform-specific routing, telemetry, and remote connector/tool registration remain unavailable.
Version-bump policy
The browser fallback X-App-Version is currently '2.0.0'. Desktop packages
use their packaged version instead.
Bump APP_VERSION_HEADER_VALUE only when a future browser-facing cloud gate
requires it. A typical coordinated change is:
- Cloud raises a new gate, for example minimum version
2.1.0. - Web bumps
APP_VERSION_HEADER_VALUEto2.1.0in the same rollout. - The PR makes the cloud-gate dependency explicit so the server side ships first.
Desktop release versions follow the desktop release workflow and do not require changing the browser constant.
Missing or unrecognized values
A missing or unrecognized X-Client-Platform is omitted from normalized
downstream context. Authentication still depends on the route's bearer/session
contract, but platform-specific routing and telemetry cannot rely on the value.
For routes with an active version gate, a missing or below-minimum
X-App-Version can return 426 Upgrade Required. Inspect the captured response
for the exact gate and minimum version rather than assuming every route enforces
the same policy.
Related
- Auth interaction architecture — the auth layer that
consumes
X-Client-Platform - API codegen — how the generated
@hey-api/client-fetchclient is configured - WebView Bridge environment detection
— the synchronous
linux/macos/windowsruntime platform contract
OpalShaderShell
Three auth surfaces (`/login`, `/oauth/select-account`, `/oauth/consent`) share one shell that bundles the static auth background, the centered content column, and the beacon plumbing the brand mark needs to paint.
API codegen
TypeScript clients are generated from the backend's OpenAPI spec via `@hey-api/openapi-ts`. `pnpm api:generate` writes to `packages/api-client/src/generated/` — never edit those files by hand.