@@ -856,8 +856,12 @@ func openClaude(t *terminal.Terminal, sshAlias string, path string, claudeArgs [
856856 return breverrors .WrapAndTrace (err )
857857 }
858858
859- // Auto-authenticate: only forward a key if the remote is not already logged in
859+ // Auto-authenticate: try API key first, then OAuth token transfer
860860 apiKey := resolveClaudeAPIKey (t , sshAlias )
861+ if apiKey == "" {
862+ // No API key found; try transferring OAuth session from local credentials
863+ tryTransferClaudeOAuthSession (t , sshAlias )
864+ }
861865
862866 sessionName := "claude"
863867
@@ -928,6 +932,59 @@ func isRemoteClaudeAuthenticated(sshAlias string) bool {
928932 return checkCmd .Run () == nil
929933}
930934
935+ // tryTransferClaudeOAuthSession checks for a local ~/.claude/.credentials.json
936+ // and offers to transfer it to the remote instance. This is a session transfer:
937+ // the local file is removed after copying so the token is only active on
938+ // the remote machine.
939+ func tryTransferClaudeOAuthSession (t * terminal.Terminal , sshAlias string ) {
940+ homeDir , err := os .UserHomeDir ()
941+ if err != nil {
942+ return
943+ }
944+ localCredPath := homeDir + "/.claude/.credentials.json"
945+
946+ // Check if local credentials file exists
947+ if _ , err := os .Stat (localCredPath ); os .IsNotExist (err ) {
948+ return
949+ }
950+
951+ t .Vprintf ("%s" , t .Yellow ("\n Found Claude Code OAuth session in ~/.claude/.credentials.json\n " ))
952+ t .Vprintf ("%s" , t .Yellow ("Transferring this session will move your auth to the remote instance\n " ))
953+ t .Vprintf ("%s" , t .Yellow ("and log you out locally (the token can only be active in one place).\n \n " ))
954+
955+ result := terminal .PromptSelectInput (terminal.PromptSelectContent {
956+ Label : "Transfer your Claude Code OAuth session to the remote instance?" ,
957+ Items : []string {"Yes, transfer and log out locally" , "No, skip" },
958+ })
959+
960+ if result != "Yes, transfer and log out locally" {
961+ return
962+ }
963+
964+ // Ensure remote ~/.claude directory exists
965+ mkdirCmd := exec .Command ("ssh" , sshAlias , `mkdir -p "$HOME/.claude"` ) // #nosec G204
966+ if err := mkdirCmd .Run (); err != nil {
967+ t .Vprintf (t .Red ("Failed to create remote ~/.claude directory: %v\n " ), err )
968+ return
969+ }
970+
971+ // SCP the credentials file to remote
972+ scpCmd := exec .Command ("scp" , localCredPath , sshAlias + ":~/.claude/.credentials.json" ) // #nosec G204
973+ output , err := scpCmd .CombinedOutput ()
974+ if err != nil {
975+ t .Vprintf (t .Red ("Failed to transfer credentials: %s\n %s\n " ), err , string (output ))
976+ return
977+ }
978+
979+ // Remove local credentials file
980+ if err := os .Remove (localCredPath ); err != nil {
981+ t .Vprintf (t .Red ("Transferred to remote but failed to remove local credentials: %v\n " ), err )
982+ return
983+ }
984+
985+ t .Vprintf ("%s" , t .Green ("OAuth session transferred to remote instance. You are now logged out locally.\n " ))
986+ }
987+
931988// getClaudeKeyFromKeychain reads the API key stored by Claude Code in the
932989// macOS Keychain (security framework).
933990func getClaudeKeyFromKeychain () (string , error ) {
0 commit comments