@@ -1099,8 +1099,12 @@ func openCodex(t *terminal.Terminal, sshAlias string, path string, codexArgs []s
10991099 return breverrors .WrapAndTrace (err )
11001100 }
11011101
1102- // Auto-authenticate: only forward a key if the remote is not already logged in
1102+ // Auto-authenticate: try API key first, then OAuth token transfer
11031103 apiKey := resolveCodexAPIKey (t , sshAlias )
1104+ if apiKey == "" {
1105+ // No API key found; try transferring OAuth session from local auth.json
1106+ tryTransferCodexOAuthSession (t , sshAlias )
1107+ }
11041108
11051109 sessionName := "codex"
11061110
@@ -1152,15 +1156,68 @@ func resolveCodexAPIKey(t *terminal.Terminal, sshAlias string) string {
11521156}
11531157
11541158// isRemoteCodexAuthenticated checks whether the remote already has
1155- // OPENAI_API_KEY set in the shell.
1159+ // OPENAI_API_KEY set in the shell or an OAuth session in ~/.codex/auth.json .
11561160func isRemoteCodexAuthenticated (sshAlias string ) bool {
11571161 checkCmd := exec .Command (
11581162 "ssh" , sshAlias ,
1159- `printenv OPENAI_API_KEY >/dev/null 2>&1` ,
1163+ `printenv OPENAI_API_KEY >/dev/null 2>&1 || test -f "$HOME/.codex/auth.json" ` ,
11601164 ) // #nosec G204
11611165 return checkCmd .Run () == nil
11621166}
11631167
1168+ // tryTransferCodexOAuthSession checks for a local ~/.codex/auth.json and
1169+ // offers to transfer it to the remote instance. This is a session transfer:
1170+ // the local file is removed after copying so the token is only active on
1171+ // the remote machine.
1172+ func tryTransferCodexOAuthSession (t * terminal.Terminal , sshAlias string ) {
1173+ homeDir , err := os .UserHomeDir ()
1174+ if err != nil {
1175+ return
1176+ }
1177+ localAuthPath := homeDir + "/.codex/auth.json"
1178+
1179+ // Check if local auth.json exists
1180+ if _ , err := os .Stat (localAuthPath ); os .IsNotExist (err ) {
1181+ return
1182+ }
1183+
1184+ t .Vprintf ("%s" , t .Yellow ("\n Found Codex OAuth session in ~/.codex/auth.json\n " ))
1185+ t .Vprintf ("%s" , t .Yellow ("Transferring this session will move your auth to the remote instance\n " ))
1186+ t .Vprintf ("%s" , t .Yellow ("and log you out locally (the token can only be active in one place).\n \n " ))
1187+
1188+ result := terminal .PromptSelectInput (terminal.PromptSelectContent {
1189+ Label : "Transfer your Codex OAuth session to the remote instance?" ,
1190+ Items : []string {"Yes, transfer and log out locally" , "No, skip" },
1191+ })
1192+
1193+ if result != "Yes, transfer and log out locally" {
1194+ return
1195+ }
1196+
1197+ // Ensure remote ~/.codex directory exists
1198+ mkdirCmd := exec .Command ("ssh" , sshAlias , `mkdir -p "$HOME/.codex"` ) // #nosec G204
1199+ if err := mkdirCmd .Run (); err != nil {
1200+ t .Vprintf (t .Red ("Failed to create remote ~/.codex directory: %v\n " ), err )
1201+ return
1202+ }
1203+
1204+ // SCP the auth.json to remote
1205+ scpCmd := exec .Command ("scp" , localAuthPath , sshAlias + ":~/.codex/auth.json" ) // #nosec G204
1206+ output , err := scpCmd .CombinedOutput ()
1207+ if err != nil {
1208+ t .Vprintf (t .Red ("Failed to transfer auth.json: %s\n %s\n " ), err , string (output ))
1209+ return
1210+ }
1211+
1212+ // Remove local auth.json
1213+ if err := os .Remove (localAuthPath ); err != nil {
1214+ t .Vprintf (t .Red ("Transferred to remote but failed to remove local auth.json: %v\n " ), err )
1215+ return
1216+ }
1217+
1218+ t .Vprintf ("%s" , t .Green ("OAuth session transferred to remote instance. You are now logged out locally.\n " ))
1219+ }
1220+
11641221func ensureCodexInstalled (t * terminal.Terminal , sshAlias string ) error {
11651222 checkCmd := fmt .Sprintf (
11661223 "ssh %s 'export NVM_DIR=\" $HOME/.nvm\" ; [ -s \" $NVM_DIR/nvm.sh\" ] && . \" $NVM_DIR/nvm.sh\" ; export PATH=\" $HOME/.local/bin:$HOME/.npm-global/bin:$PATH\" ; which codex >/dev/null 2>&1'" ,
0 commit comments