---
type: research
title: User Agents — Every UA the Desktop App and Its Clients Send
description: The complete inventory of User-Agent values observed leaving the ChatGPT desktop: app main, sentinel fallback, plugin runtime installer, probes — and why each matters for a standalone
tags: [openai, user-agent, http, chatgpt, standalone, headers]
timestamp: 2026-08-26
---

# User Agents — Every UA the Desktop App and Its Clients Send

Part of [openai-research](openai-research.md). A standalone must present the right `User-Agent` per surface — servers route/validate by client type (this is also hypothesis for detections). Captured UAs, 2026-08-26:

| Context | UA | Notes |
|---|---|---|
| Main process requests (chat submode flow) | **`Codex Desktop/26.820.60940 (Mac OS; arm64)`** | `X-OpenAI-Attach-*` headers, `originator: Codex Desktop`; app self-updated 26.818 → 26.820 during session |
| Sentinel fallback (flip win32/x64) | **`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36`** | With full `sec-ch-ua` set + `sec-ch-ua-platform: "macOS"`; `originator: Codex Browser` (NOT Desktop!) |
| Plugin primary runtime download | **`codex-primary-runtime-installer`** | `GET persistent.oaistatic.com/codex-primary-runtime/26.826.11250/codex-primary-runtime-darwin-arm64-26.826.11250.tar.xz` — on-demand plugin runtime (see architecture) |
| Bezetacil hooks | none (they run in-process) | — |
| Legacy probe files | `dsh-openai-probe/0.1 (security research)` | stale probes only (not a real client identity) |
| Web (site/browser route) | browser-consistent (the user's Safari etc.) | the route that uses cookies + sentinel web PoW |

Details that matter for a standalone:

- **Version strings are part of the identity**: `Codex Desktop/26.820.60940 (Mac OS; arm64)` — the `client_version` in `/codex/models?client_version=…` tracks the **binary build**; if the requirement is "binary installed and updated", the harness should mirror the installed app version.
- The **sentinel fallback** swaps in a *browser* UA + `Codex Browser` originator (`sec-ch-ua` says Chrome/151) while the fingerprint continues to carry the real webview chunk (`app://-/assets/index-*.js`) — a deliberately mixed identity (desktop renderer with browser frames).
- The **rust side** (app-server) does not set a UA of its own in the observed requests (owned `codex_http_client` requests to `/backend-api/codex/*` carry the API path + attestation headers; UA default hyper/reqwest, not observed in traces). `X-OAI-IS`: not observed in the executed flows (A/A1/manual); flow B: unknown (not executed).

## TypeScript types (the UA surface)

```ts
/** The main-process desktop UA — version is the app build (getVersion). */
export interface DesktopUa {
  product: `Codex Desktop/${string}`
  platform: 'Mac OS; arm64' | string
  toString(): string                    // 'Codex Desktop/26.820.60940 (Mac OS; arm64)'
}
/** Builder: `Codex Desktop/${app.getVersion()} (Mac OS; arm64)` */

/** The sentinel-fallback browser UA (flip). */
export interface BrowserUa {
  product: 'Mozilla/5.0'
  platform: '(Macintosh; Intel Mac OS X 10_15_7)'     // the observed flip value
  engine: 'AppleWebKit/537.36 (KHTML, like Gecko)'
  chrome: 'Chrome/151.0.0.0'
  safari: 'Safari/537.36'
  secChUa: '"Chromium";v="151", "Google Chrome";v="151", "Not=A?Brand";v="24"'
  secChUaMobile: '?0'
  secChUaPlatform: '"macOS"'
}
export const CDPX_UA_HEADER = 'codex-primary-runtime-installer'   // plugin runtime downloader
export const ORIGINATORS = { desktop: 'Codex Desktop', browser: 'Codex Browser', cli: 'codex_cli_rs' } as const
```

## Cross-references

- [chatgpt-desktop-architecture](chatgpt-desktop-architecture.md) — where each UA goes (main vs renderer vs app-server)
- [sentinel-fallback-validation](sentinel-fallback-validation.md) — the fallback UA in action
- [chat-submode-protocol](chat-submode-protocol.md) — the chat submode headers
- [flow-a-manual-step-by-step](flow-a-manual-step-by-step.md) — the validated header set for flow A1
