Skip to content

Commit 616d25c

Browse files
committed
Pin agent identities to ChatGPT users
1 parent 46037d7 commit 616d25c

4 files changed

Lines changed: 56 additions & 1 deletion

File tree

codex-rs/core/src/agent_identity.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ impl StoredAgentIdentity {
331331
Ok(Self {
332332
binding_id: binding.binding_id.clone(),
333333
chatgpt_account_id: binding.chatgpt_account_id.clone(),
334-
chatgpt_user_id: binding.chatgpt_user_id.clone(),
334+
chatgpt_user_id: record.chatgpt_user_id,
335335
agent_runtime_id: record.agent_runtime_id,
336336
private_key_pkcs8_base64: record.agent_private_key,
337337
public_key_ssh: encode_ssh_ed25519_public_key(&signing_key.verifying_key()),
@@ -343,6 +343,7 @@ impl StoredAgentIdentity {
343343
fn to_auth_record(&self) -> AgentIdentityAuthRecord {
344344
AgentIdentityAuthRecord {
345345
workspace_id: self.chatgpt_account_id.clone(),
346+
chatgpt_user_id: self.chatgpt_user_id.clone(),
346347
agent_runtime_id: self.agent_runtime_id.clone(),
347348
agent_private_key: self.private_key_pkcs8_base64.clone(),
348349
registered_at: self.registered_at.clone(),
@@ -589,6 +590,7 @@ mod tests {
589590
AgentIdentityBinding::from_auth(&auth, /*forced_workspace_id*/ None).expect("binding");
590591
auth.set_agent_identity(AgentIdentityAuthRecord {
591592
workspace_id: "account-123".to_string(),
593+
chatgpt_user_id: Some("user-123".to_string()),
592594
agent_runtime_id: "agent_invalid".to_string(),
593595
agent_private_key: "not-valid-base64".to_string(),
594596
registered_at: "2026-01-01T00:00:00Z".to_string(),
@@ -608,6 +610,55 @@ mod tests {
608610
assert_eq!(persisted.agent_runtime_id, "agent_456");
609611
}
610612

613+
#[tokio::test]
614+
async fn ensure_registered_identity_deletes_different_user_identity_and_reregisters() {
615+
let server = MockServer::start().await;
616+
let chatgpt_base_url = server.uri();
617+
mount_human_biscuit(&server, &chatgpt_base_url).await;
618+
Mock::given(method("POST"))
619+
.and(path("/v1/agent/register"))
620+
.and(header("x-openai-authorization", "human-biscuit"))
621+
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
622+
"agent_runtime_id": "agent_new",
623+
})))
624+
.expect(1)
625+
.mount(&server)
626+
.await;
627+
628+
let auth = make_chatgpt_auth("account-123", Some("user-new"));
629+
let stale_key = generate_agent_key_material().expect("key material");
630+
auth.set_agent_identity(AgentIdentityAuthRecord {
631+
workspace_id: "account-123".to_string(),
632+
chatgpt_user_id: Some("user-old".to_string()),
633+
agent_runtime_id: "agent_old".to_string(),
634+
agent_private_key: stale_key.private_key_pkcs8_base64,
635+
registered_at: "2026-01-01T00:00:00Z".to_string(),
636+
})
637+
.expect("seed stale identity");
638+
639+
let auth_manager = AuthManager::from_auth_for_testing(auth.clone());
640+
let manager = AgentIdentityManager::new_for_tests(
641+
auth_manager,
642+
/*feature_enabled*/ true,
643+
chatgpt_base_url,
644+
SessionSource::Cli,
645+
);
646+
647+
let stored = manager
648+
.ensure_registered_identity()
649+
.await
650+
.unwrap()
651+
.expect("identity should be registered");
652+
653+
assert_eq!(stored.agent_runtime_id, "agent_new");
654+
assert_eq!(stored.chatgpt_user_id.as_deref(), Some("user-new"));
655+
let persisted = auth
656+
.get_agent_identity("account-123")
657+
.expect("stored identity");
658+
assert_eq!(persisted.agent_runtime_id, "agent_new");
659+
assert_eq!(persisted.chatgpt_user_id.as_deref(), Some("user-new"));
660+
}
661+
611662
#[tokio::test]
612663
async fn ensure_registered_identity_uses_chatgpt_base_url() {
613664
let server = MockServer::start().await;

codex-rs/login/src/auth/auth_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ fn chatgpt_auth_persists_agent_identity_for_workspace() {
206206
.expect("auth available");
207207
let record = AgentIdentityAuthRecord {
208208
workspace_id: "account-123".to_string(),
209+
chatgpt_user_id: Some("user-123".to_string()),
209210
agent_runtime_id: "agent_123".to_string(),
210211
agent_private_key: "pkcs8-base64".to_string(),
211212
registered_at: "2026-04-13T12:00:00Z".to_string(),

codex-rs/login/src/auth/storage.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ pub struct AuthDotJson {
4747
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
4848
pub struct AgentIdentityAuthRecord {
4949
pub workspace_id: String,
50+
#[serde(default, skip_serializing_if = "Option::is_none")]
51+
pub chatgpt_user_id: Option<String>,
5052
pub agent_runtime_id: String,
5153
pub agent_private_key: String,
5254
pub registered_at: String,

codex-rs/login/src/auth/storage_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ async fn file_storage_persists_agent_identity() -> anyhow::Result<()> {
6565
last_refresh: Some(Utc::now()),
6666
agent_identity: Some(AgentIdentityAuthRecord {
6767
workspace_id: "account-123".to_string(),
68+
chatgpt_user_id: Some("user-123".to_string()),
6869
agent_runtime_id: "agent_123".to_string(),
6970
agent_private_key: "pkcs8-base64".to_string(),
7071
registered_at: "2026-04-13T12:00:00Z".to_string(),

0 commit comments

Comments
 (0)