Skip to content

Commit 5074943

Browse files
committed
feat: add Codex OAuth session transfer from local to remote
1 parent da31b40 commit 5074943

1 file changed

Lines changed: 60 additions & 3 deletions

File tree

pkg/cmd/open/open.go

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
10461050
func 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("\nFound 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+
10541111
func 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

Comments
 (0)