@@ -989,8 +989,12 @@ func openCodex(t *terminal.Terminal, sshAlias string, path string, codexArgs []s
989989 return breverrors .WrapAndTrace (err )
990990 }
991991
992- // Auto-authenticate: only forward a key if the remote is not already logged in
992+ // Auto-authenticate: try API key first, then OAuth token transfer
993993 apiKey := resolveCodexAPIKey (t , sshAlias )
994+ if apiKey == "" {
995+ // No API key found; try transferring OAuth session from local auth.json
996+ tryTransferCodexOAuthSession (t , sshAlias )
997+ }
994998
995999 sessionName := "codex"
9961000
@@ -1042,15 +1046,68 @@ func resolveCodexAPIKey(t *terminal.Terminal, sshAlias string) string {
10421046}
10431047
10441048// isRemoteCodexAuthenticated checks whether the remote already has
1045- // OPENAI_API_KEY set in the shell.
1049+ // OPENAI_API_KEY set in the shell or an OAuth session in ~/.codex/auth.json .
10461050func isRemoteCodexAuthenticated (sshAlias string ) bool {
10471051 checkCmd := exec .Command (
10481052 "ssh" , sshAlias ,
1049- `printenv OPENAI_API_KEY >/dev/null 2>&1` ,
1053+ `printenv OPENAI_API_KEY >/dev/null 2>&1 || test -f "$HOME/.codex/auth.json" ` ,
10501054 ) // #nosec G204
10511055 return checkCmd .Run () == nil
10521056}
10531057
1058+ // tryTransferCodexOAuthSession checks for a local ~/.codex/auth.json and
1059+ // offers to transfer it to the remote instance. This is a session transfer:
1060+ // the local file is removed after copying so the token is only active on
1061+ // the remote machine.
1062+ func tryTransferCodexOAuthSession (t * terminal.Terminal , sshAlias string ) {
1063+ homeDir , err := os .UserHomeDir ()
1064+ if err != nil {
1065+ return
1066+ }
1067+ localAuthPath := homeDir + "/.codex/auth.json"
1068+
1069+ // Check if local auth.json exists
1070+ if _ , err := os .Stat (localAuthPath ); os .IsNotExist (err ) {
1071+ return
1072+ }
1073+
1074+ t .Vprintf ("%s" , t .Yellow ("\n Found Codex OAuth session in ~/.codex/auth.json\n " ))
1075+ t .Vprintf ("%s" , t .Yellow ("Transferring this session will move your auth to the remote instance\n " ))
1076+ t .Vprintf ("%s" , t .Yellow ("and log you out locally (the token can only be active in one place).\n \n " ))
1077+
1078+ result := terminal .PromptSelectInput (terminal.PromptSelectContent {
1079+ Label : "Transfer your Codex OAuth session to the remote instance?" ,
1080+ Items : []string {"Yes, transfer and log out locally" , "No, skip" },
1081+ })
1082+
1083+ if result != "Yes, transfer and log out locally" {
1084+ return
1085+ }
1086+
1087+ // Ensure remote ~/.codex directory exists
1088+ mkdirCmd := exec .Command ("ssh" , sshAlias , `mkdir -p "$HOME/.codex"` ) // #nosec G204
1089+ if err := mkdirCmd .Run (); err != nil {
1090+ t .Vprintf (t .Red ("Failed to create remote ~/.codex directory: %v\n " ), err )
1091+ return
1092+ }
1093+
1094+ // SCP the auth.json to remote
1095+ scpCmd := exec .Command ("scp" , localAuthPath , sshAlias + ":~/.codex/auth.json" ) // #nosec G204
1096+ output , err := scpCmd .CombinedOutput ()
1097+ if err != nil {
1098+ t .Vprintf (t .Red ("Failed to transfer auth.json: %s\n %s\n " ), err , string (output ))
1099+ return
1100+ }
1101+
1102+ // Remove local auth.json
1103+ if err := os .Remove (localAuthPath ); err != nil {
1104+ t .Vprintf (t .Red ("Transferred to remote but failed to remove local auth.json: %v\n " ), err )
1105+ return
1106+ }
1107+
1108+ t .Vprintf ("%s" , t .Green ("OAuth session transferred to remote instance. You are now logged out locally.\n " ))
1109+ }
1110+
10541111func ensureCodexInstalled (t * terminal.Terminal , sshAlias string ) error {
10551112 checkCmd := fmt .Sprintf (
10561113 "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