9 Commits

  • Allow ChatGPT accounts without email (#28991)
    # Summary
    
    Codex required every ChatGPT account to have an email address. A
    service-account personal access token can return valid account metadata
    without one, so PAT login failed while decoding the metadata response.
    
    This change makes email optional in the account metadata type that owns
    it and preserves that absence through authentication, provider account
    state, the app-server API, generated clients, and TUI bootstrap.
    Existing accounts with email addresses keep the same behavior.
    
    ## Behavior-changing call sites
    
    | Call site | Behavior after this change |
    | --- | --- |
    | `login/src/auth/personal_access_token.rs` | PAT metadata accepts a
    missing or null email and retains `None`. |
    | `agent-identity/src/lib.rs` | Agent Identity JWT claims accept an
    omitted email. |
    | `login/src/auth/storage.rs` and `login/src/auth/agent_identity.rs` |
    Stored and managed Agent Identity records carry `Option<String>`.
    Deserialization maps the legacy empty-string sentinel to `None`. |
    | `login/src/auth/manager.rs` | `get_account_email` returns the stored
    option, and managed identity bootstrap no longer converts `None` to an
    empty string. |
    | `model-provider/src/provider.rs` and `protocol/src/account.rs` | A
    ChatGPT provider account requires a plan type but may carry no email. |
    | `app-server-protocol/src/protocol/v2/account.rs` | `account/read`
    keeps the `email` field on the wire and returns `null` when the account
    has no email. Generated TypeScript and JSON schemas describe a required,
    nullable field. |
    | `sdk/python/src/openai_codex/generated/v2_all.py` | The generated
    Python `ChatgptAccount` model accepts `None` for email. |
    | `tui/src/app_server_session.rs` | Email-less ChatGPT accounts
    bootstrap normally, keep external feedback routing, omit account-email
    telemetry, and display the plan in account status. |
    
    ## Design decisions
    
    - Missing email remains `None` at every layer. The code never uses an
    empty string as a substitute.
    - The app-server response includes `"email": null` instead of omitting
    the field. Clients retain a stable response shape.
    - Plan type remains required for provider account state. This change
    relaxes only the email assumption.
    
    ## Testing
    
    Tests: affected test targets compile, scoped Clippy and formatting pass,
    a focused TUI snapshot covers plan-only account status, real
    before/after PAT login smoke covers metadata without email, app-server
    smoke covers `account/read` with `email: null`, and a regression smoke
    covers an existing email-bearing PAT. Unit tests run in CI.
    
    ## Evidence
    
    Visual smoke evidence will be attached here.
  • PAC 2 - Add shared auth system proxy contract (#26707)
    ## Summary
    
    Stacked on #26706.
    
    Adds the shared auth/system-proxy contract that later platform resolver
    PRs plug into. This PR moves Codex-owned auth and startup HTTP clients
    through a common route-aware boundary, but does not yet add Windows or
    macOS system proxy resolution.
    
    The default path remains unchanged when `respect_system_proxy` is absent
    or disabled.
    
    ## Implementation
    
    - Adds `codex-client/src/outbound_proxy.rs` with the shared
    route-selection model:
      - `OutboundProxyConfig`;
      - `ClientRouteClass`;
      - `RouteFailureClass`;
      - `build_reqwest_client_for_route`.
    - Preserves the existing reqwest/default-client behavior when no route
    config is supplied.
    - Uses the fixed MVP routing policy when route config is supplied:
    platform system/PAC/WPAD discovery, then explicit env proxy variables,
    then direct connection.
    - Keeps platform-specific system discovery behind the shared client
    boundary. This PR provides the contract and fallback behavior; later
    resolver PRs plug in Windows and macOS discovery.
    - Adds `login::AuthRouteConfig` so auth call sites depend on a small
    policy type instead of platform resolver details.
    - Maps the resolved `Config.respect_system_proxy` boolean into
    `AuthRouteConfig` for auth-owned clients.
    - Wires the route config through browser login, device-code login,
    access-token login, login status, logout/revoke, token refresh, API-key
    exchange, app-server account login, TUI/app startup, cloud-config
    bootstrap, cloud tasks, plugin auth, and exec startup config loading.
    
    ## End-user behavior
    
    - No behavior changes by default.
    - When `respect_system_proxy = true`, auth-owned clients opt into the
    shared route-aware client path.
    - On platforms without a resolver implementation in this PR, system
    discovery is unavailable and the route-aware path falls back to explicit
    env proxy handling, then direct connection.
    - Custom CA handling remains separate from proxy route selection and
    still runs through the shared client builder.
    - No proxy URLs, PAC contents, or resolved platform details are exposed
    through the public config surface introduced here.
    
    ## Tests
    
    Adds or updates coverage for:
    
    - preserving default auth-client fallback behavior when no route config
    is provided;
    - injected environment-proxy fallback without mutating process
    environment;
    - existing login-server E2E flows using explicit `auth_route_config:
    None` to guard unchanged default behavior;
    - updated auth manager, login, logout, cloud-config, startup, and
    plugin-auth call sites passing route config explicitly.
  • feat: opt ChatGPT auth into agent identity (#19049)
    ## Stack
    
    This is PR 2 of the simplified HAI single-run-task stack:
    
    - [#19047](https://github.com/openai/codex/pull/19047) Agent Identity
    assertion and task-registration primitives, including the shared
    run-task helper used by existing Agent Identity JWT auth.
    - [#19049](https://github.com/openai/codex/pull/19049)
    Disabled-by-default ChatGPT auth opt-in that provisions/reuses persisted
    Agent Identity runtime auth and its single run task.
    - [#19051](https://github.com/openai/codex/pull/19051) Run-scoped
    provider auth that uses one backend-owned task id for first-party
    inference and compaction requests.
    
    [#19054](https://github.com/openai/codex/pull/19054) collapsed out of
    the active stack because the simplified design no longer needs a
    separate background/control-plane task helper.
    
    ## Summary
    
    This PR adds the disabled-by-default path for normal ChatGPT-login Codex
    sessions to obtain Agent Identity runtime auth through the Codex
    backend. Existing Agent Identity JWT startup mode remains a separate
    path and does not require the feature flag.
    
    What changed:
    
    - adds the experimental `use_agent_identity` feature flag and config
    schema entry
    - adds an explicit `AgentIdentityAuthPolicy` so call sites choose
    `JwtOnly` or `ChatGptAuth` instead of passing a bare boolean
    - stores standalone Agent Identity JWT credentials separately from
    backend-registered Agent Identity records
    - persists the registered Agent Identity record, private key, and single
    run task id in `auth.json` so process restarts reuse the same identity
    - derives the agent/task registration base URL from ChatGPT/Codex auth
    config while keeping JWT JWKS lookup separate
    - provisions and caches ChatGPT-derived Agent Identity runtime auth when
    `use_agent_identity` is enabled
    - reuses the shared run-task registration helper from PR1 rather than
    adding a second task-registration path
    
    This PR intentionally does not switch model inference over to
    `AgentAssertion` auth. The provider-auth integration lands in the next
    PR.
    
    ## Testing
    
    - `just test -p codex-login`
  • feat: use encrypted local secrets for CLI auth (#27539)
    ## Why
    
    Windows Credential Manager limits generic credential blobs to 2,560
    bytes. Large serialized ChatGPT auth payloads can exceed that limit, so
    keyring-mode CLI auth needs a backend that keeps only the encryption key
    in the OS keyring and stores the payload in Codex's encrypted
    local-secrets file.
    
    This is the third PR in the encrypted-auth stack:
    
    1. #27504 — feature and config selection
    2. #27535 — auth-specific local-secrets namespaces
    3. This PR — CLI auth implementation and activation
    4. MCP OAuth implementation and activation
    
    ## What Changed
    
    - Added encrypted CLI-auth storage using the `CliAuth` secrets
    namespace.
    - Preserved direct keyring storage for platforms/configurations where it
    remains selected.
    - Selected the backend consistently for login, logout, refresh,
    device-code login, auth loading, and login restrictions.
    - Threaded resolved bootstrap/full config through CLI, exec, TUI,
    app-server account handling, cloud config, and cloud tasks.
    - Removed stale `auth.json` fallback data after successful encrypted
    saves and removed encrypted, direct-keyring, and fallback data during
    logout.
    - Added storage and integration coverage for both direct and encrypted
    keyring modes.
    
    MCP OAuth persistence is intentionally left to the next PR.
    
    ## Validation
    
    - `just test -p codex-login` — 131 passed
    - `just test -p codex-cli` — 280 passed
    - `just test -p codex-app-server v2::account` — 25 passed
    - `just test -p codex-cloud-config service` — 21 passed, 7 skipped
    - `just fix -p codex-login`
    - `just fix -p codex-cli`
    - `just fmt`
  • [codex] Tune cloud config cache intervals (#26513)
    ## Summary
    - Increase the cloud config bundle background refresh interval from 5
    minutes to 15 minutes.
    - Increase the local cloud config bundle cache TTL from 30 minutes to 1
    hour.
    
    ## Why
    - Reduce background cloud config fetch frequency while keeping cached
    workspace-managed policies available longer between refreshes.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-cloud-config`
  • Allow EDU accounts to fetch cloud config bundles (#25963)
    ## Summary
    
    Allow EDU ChatGPT workspaces to fetch cloud config bundles. The existing
    cloud config eligibility gate only allowed business-like and enterprise
    plans, which meant EDU admins could configure managed policies in the UI
    but the Codex client would skip fetching them.
    
    This keeps individual/pro and team-like usage-based plans excluded, and
    adds service-level coverage for both `edu` and `education` plan aliases.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-cloud-config`
    - Built the Codex app locally, created a new EDU ChatGPT workspace, and
    verified config bundles can be fetched and are properly applied.
  • Split cloud config bundle service modules (#25668)
    ## Summary
    
    - Splits the monolithic `codex-cloud-config` implementation into focused
    modules.
    - Keeps behavior unchanged from the preceding config bundle runtime
    switch.
    
    ## Details
    
    This is the reviewability follow-up after the lineage-preserving
    migration PRs. The split separates backend transport, loader
    construction, cache handling, metrics, validation, service
    orchestration, and focused tests into named files.
    
    Verification: `just fmt`; `just test -p codex-cloud-config`.
  • Switch runtime to cloud config bundle (#24622)
    ## Summary
    
    - Adapts the moved `codex-cloud-config` crate from the legacy cloud
    requirements endpoint to the new config bundle endpoint.
    - Switches runtime consumers from `CloudRequirementsLoader` to
    `CloudConfigBundleLoader` so one shared bundle supplies cloud-delivered
    config and requirements.
    - Removes the legacy cloud requirements domain loader path.
    
    ## Details
    
    This intentionally keeps `codex-cloud-config` monolithic for review
    lineage: the previous PR establishes the crate move, and this PR shows
    the behavior change against that moved implementation. A follow-up PR
    splits the module back into focused files.
    
    The new bundle path preserves the important cloud requirements loader
    semantics where intended: account-scoped signed cache, 30 minute TTL, 5
    minute refresh cadence, retry/backoff, auth recovery, and fail-closed
    startup loading. The cached payload changes from a single requirements
    TOML string to the backend-delivered bundle, and validation rejects
    malformed config or requirements fragments before cache write/use.
  • Move cloud requirements crate to cloud config (#24621)
    ## Summary
    
    - Moves the existing `codex-cloud-requirements` crate to
    `codex-cloud-config`.
    - Updates workspace dependencies and imports to the new crate name.
    - Intentionally keeps runtime behavior unchanged: this still fetches the
    legacy cloud requirements endpoint.
    
    ## Details
    
    This PR exists to make the lineage obvious before the bundle migration.
    GitHub should show the old `codex-rs/cloud-requirements/src/lib.rs`
    implementation as moved to `codex-rs/cloud-config/src/lib.rs`, rather
    than as unrelated new code.
    
    The follow-up PR adapts this moved crate to the new config bundle API
    and switches runtime consumers over.