feat: load AgentIdentity from JWT login/env (#18904)

## Summary

This PR lets programmatic AgentIdentity users provide one token through
either stdin login or environment auth.

`codex login --with-agent-identity` reads an Agent Identity JWT from
stdin, validates that it has the required claims, and stores that token
as the `agent_identity` value in `auth.json`. The file format is
token-only; the decoded account and key fields are runtime state, not
hand-authored auth.json fields.

The Agent Identity JWT claim shape and decoder live in
`codex-agent-identity`; `codex-login` only owns env/storage precedence
and conversion into `CodexAuth::AgentIdentity`.

When env auth is enabled, `CODEX_AGENT_IDENTITY` can provide the same
JWT without writing auth state to disk. `CODEX_API_KEY` still wins if
both env vars are set.

Reference old stack: https://github.com/openai/codex/pull/17387/changes
Reference JWT/env stack: https://github.com/openai/codex/pull/18176

## Stack

1. https://github.com/openai/codex/pull/18757: full revert
2. https://github.com/openai/codex/pull/18871: isolated Agent Identity
crate
3. https://github.com/openai/codex/pull/18785: explicit AgentIdentity
auth mode and startup task allocation
4. https://github.com/openai/codex/pull/18811: migrate Codex backend
auth callsites through AuthProvider
5. This PR: accept AgentIdentity JWTs through login/env

## Testing

Tests: targeted login and Agent Identity crate tests, CLI checks, scoped
formatter/linter cleanup, and CI.

---------

Co-authored-by: Shijie Rao <shijie.rao@openai.com>
This commit is contained in:
efrazer-oaiandShijie Rao authored and GitHub committed 2026-04-26 19:49:54 +00:00
1 parent ac2bffa443
commit fed0a8f4fa
14 files changed
+587 -49

No files matched your search

+19 -1
View File
@@ -19,6 +19,7 @@ use std::sync::Mutex;
use tracing::warn;
use crate::token_data::TokenData;
use codex_agent_identity::decode_agent_identity_jwt;
use codex_app_server_protocol::AuthMode;
use codex_config::types::AuthCredentialsStoreMode;
use codex_keyring_store::DefaultKeyringStore;
@@ -42,7 +43,7 @@ pub struct AuthDotJson {
pub last_refresh: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent_identity: Option<AgentIdentityAuthRecord>,
pub agent_identity: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
@@ -56,6 +57,23 @@ pub struct AgentIdentityAuthRecord {
pub chatgpt_account_is_fedramp: bool,
}
impl AgentIdentityAuthRecord {
pub(crate) fn from_agent_identity_jwt(jwt: &str) -> std::io::Result<Self> {
let claims = decode_agent_identity_jwt(jwt, /*public_key_base64*/ None)
.map_err(std::io::Error::other)?;
Ok(Self {
agent_runtime_id: claims.agent_runtime_id,
agent_private_key: claims.agent_private_key,
account_id: claims.account_id,
chatgpt_user_id: claims.chatgpt_user_id,
email: claims.email,
plan_type: claims.plan_type,
chatgpt_account_is_fedramp: claims.chatgpt_account_is_fedramp,
})
}
}
pub(super) fn get_auth_file(codex_home: &Path) -> PathBuf {
codex_home.join("auth.json")
}