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