From a0f5a72a5bc1cbb966d45d5981588cc8a7310869 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 27 May 2026 12:58:55 +0200 Subject: [PATCH 1/3] auth: stop writing workspace_id = none on --skip-workspace When `databricks auth login --skip-workspace` ran on a host with no discoverable workspace, the in-memory WorkspaceIDNone sentinel was persisted to .databrickscfg as the literal string `workspace_id = none`. The sentinel was only needed in-memory to gate the workspace prompt; it never had a reason to land on disk. SaveToProfile already omits empty fields, so just leaving WorkspaceID empty produces a clean account-only profile, and MatchAccountProfiles still recognizes existing legacy profiles that have `workspace_id = none` for backwards compat. Co-authored-by: Isaac --- cmd/auth/login.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/cmd/auth/login.go b/cmd/auth/login.go index 475a72ed36..52fa1d17f1 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -318,20 +318,17 @@ a new profile is created. authArguments.WorkspaceID == "" && !skipWorkspace - if skipWorkspace && authArguments.WorkspaceID == "" { - authArguments.WorkspaceID = auth.WorkspaceIDNone - } - if shouldPromptWorkspace { wsID, wsErr := promptForWorkspaceSelection(ctx, authArguments, persistentAuth) if wsErr != nil { log.Warnf(ctx, "Workspace selection failed: %v", wsErr) - } else if wsID == "" { - // User selected "Skip" from the prompt. - authArguments.WorkspaceID = auth.WorkspaceIDNone - } else { + } else if wsID != "" { authArguments.WorkspaceID = wsID } + // If wsID is empty, the user picked "Skip" — leave WorkspaceID empty. + // SaveToProfile omits the workspace_id key entirely for account-level + // profiles; MatchAccountProfiles treats absent workspace_id the same + // as the legacy "none" sentinel. } var clusterID, serverlessComputeID string From 734ffbf23573d9f76117030ef8e68345fed7464d Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 27 May 2026 14:04:40 +0200 Subject: [PATCH 2/3] auth: don't re-prompt for workspace when re-logging into account-only profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without writing the "none" sentinel anymore, an account-only profile loads back with WorkspaceID == "" — the same shape as a fresh login that still needs a workspace pick. The next `databricks auth login --profile foo` would prompt again on every run. Extract shouldPromptWorkspace and treat "existing profile is account-only" (account_id set, workspace_id empty or the legacy "none" sentinel) as honoring the user's prior skip choice. Covers both shapes so legacy profiles keep working alongside new ones. Co-authored-by: Isaac --- cmd/auth/login.go | 26 +++++++++---- cmd/auth/login_test.go | 63 ++++++++++++++++++++++++++++++++ cmd/auth/testdata/.databrickscfg | 4 ++ 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/cmd/auth/login.go b/cmd/auth/login.go index 52fa1d17f1..b1545afe1e 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -311,14 +311,7 @@ a new profile is created. // 2. Configuring cluster and serverless; // 3. Saving the profile. - // If discovery gave us an account_id but we still have no workspace_id, - // prompt the user to select a workspace. This applies to any host where - // .well-known/databricks-config returned an account_id. - shouldPromptWorkspace := authArguments.AccountID != "" && - authArguments.WorkspaceID == "" && - !skipWorkspace - - if shouldPromptWorkspace { + if shouldPromptWorkspace(authArguments, existingProfile, skipWorkspace) { wsID, wsErr := promptForWorkspaceSelection(ctx, authArguments, persistentAuth) if wsErr != nil { log.Warnf(ctx, "Workspace selection failed: %v", wsErr) @@ -397,6 +390,23 @@ a new profile is created. return cmd } +// shouldPromptWorkspace reports whether the login flow should ask the user to +// pick a workspace. We prompt when we have an account_id but no workspace_id +// and the user did not pass --skip-workspace, with one exception: re-login +// into an existing profile that's already account-only (account_id set, +// workspace_id absent or the legacy "none" sentinel) honors the user's prior +// "skip" choice instead of re-prompting on every login. +func shouldPromptWorkspace(authArguments *auth.AuthArguments, existingProfile *profile.Profile, skipWorkspace bool) bool { + if authArguments.AccountID == "" || authArguments.WorkspaceID != "" || skipWorkspace { + return false + } + if existingProfile != nil && existingProfile.AccountID != "" && + (existingProfile.WorkspaceID == "" || existingProfile.WorkspaceID == auth.WorkspaceIDNone) { + return false + } + return true +} + // Sets the host in the persistentAuth object based on the provided arguments and flags. // Follows the following precedence: // 1. [HOST] (first positional argument) or --host flag. Error if both are specified. diff --git a/cmd/auth/login_test.go b/cmd/auth/login_test.go index 101f2e92e5..68e038f40e 100644 --- a/cmd/auth/login_test.go +++ b/cmd/auth/login_test.go @@ -546,6 +546,69 @@ func TestSetHostAndAccountId_WorkspaceIDNoneSentinelInherited(t *testing.T) { assert.Equal(t, auth.WorkspaceIDNone, args.WorkspaceID) } +func TestShouldPromptWorkspace(t *testing.T) { + t.Setenv("DATABRICKS_CONFIG_FILE", "./testdata/.databrickscfg") + ctx, _ := cmdio.SetupTest(t.Context(), cmdio.TestOptions{}) + + legacyAccountProfile := loadTestProfile(t, ctx, "spog-skip-workspace") + newAccountProfile := loadTestProfile(t, ctx, "spog-skip-workspace-new") + workspaceProfile := loadTestProfile(t, ctx, "unified-workspace") + + tests := []struct { + name string + authArguments auth.AuthArguments + existingProfile *profile.Profile + skipWorkspace bool + want bool + }{ + { + name: "no profile, account_id set, no workspace_id", + authArguments: auth.AuthArguments{AccountID: "acc"}, + want: true, + }, + { + name: "re-login into legacy account-only profile (workspace_id = none)", + authArguments: auth.AuthArguments{AccountID: "spog-account"}, + existingProfile: legacyAccountProfile, + want: false, + }, + { + name: "re-login into new account-only profile (no workspace_id key)", + authArguments: auth.AuthArguments{AccountID: "spog-account"}, + existingProfile: newAccountProfile, + want: false, + }, + { + name: "re-login into workspace profile prompts when workspace_id missing from args", + authArguments: auth.AuthArguments{AccountID: "test-unified-account"}, + existingProfile: workspaceProfile, + want: true, + }, + { + name: "skipWorkspace suppresses the prompt", + authArguments: auth.AuthArguments{AccountID: "acc"}, + skipWorkspace: true, + want: false, + }, + { + name: "no account_id means no prompt", + authArguments: auth.AuthArguments{}, + want: false, + }, + { + name: "workspace_id already known means no prompt", + authArguments: auth.AuthArguments{AccountID: "acc", WorkspaceID: "12345"}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := shouldPromptWorkspace(&tt.authArguments, tt.existingProfile, tt.skipWorkspace) + assert.Equal(t, tt.want, got) + }) + } +} + func TestSetHostAndAccountId_URLParamsOverrideProfile(t *testing.T) { t.Setenv("DATABRICKS_CONFIG_FILE", "./testdata/.databrickscfg") ctx, _ := cmdio.SetupTest(t.Context(), cmdio.TestOptions{}) diff --git a/cmd/auth/testdata/.databrickscfg b/cmd/auth/testdata/.databrickscfg index acd227d515..cbcd36fc14 100644 --- a/cmd/auth/testdata/.databrickscfg +++ b/cmd/auth/testdata/.databrickscfg @@ -31,3 +31,7 @@ experimental_is_unified_host = true host = https://spog.example.com account_id = spog-account workspace_id = none + +[spog-skip-workspace-new] +host = https://spog.example.com +account_id = spog-account From 34a62d2bc37e373b49df4c78afa48b0b9773f186 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 27 May 2026 15:01:41 +0200 Subject: [PATCH 3/3] auth: require matching account_id when honoring prior skip-workspace GPT review caught that `shouldPromptWorkspace` honored an existing account-only profile without checking the account ID matched. Reusing a profile name against a different account would suppress the workspace prompt on the new account too. Tighten the check to require `existingProfile.AccountID == authArguments.AccountID`. Co-authored-by: Isaac --- cmd/auth/login.go | 11 +++++++---- cmd/auth/login_test.go | 6 ++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cmd/auth/login.go b/cmd/auth/login.go index b1545afe1e..512269b80b 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -393,14 +393,17 @@ a new profile is created. // shouldPromptWorkspace reports whether the login flow should ask the user to // pick a workspace. We prompt when we have an account_id but no workspace_id // and the user did not pass --skip-workspace, with one exception: re-login -// into an existing profile that's already account-only (account_id set, -// workspace_id absent or the legacy "none" sentinel) honors the user's prior -// "skip" choice instead of re-prompting on every login. +// into an existing profile that's already account-only for the SAME account +// (account_id matches and workspace_id is absent or the legacy "none" +// sentinel) honors the user's prior "skip" choice instead of re-prompting on +// every login. We require the account_id to match so reusing a profile name +// against a different account still gets the workspace prompt. func shouldPromptWorkspace(authArguments *auth.AuthArguments, existingProfile *profile.Profile, skipWorkspace bool) bool { if authArguments.AccountID == "" || authArguments.WorkspaceID != "" || skipWorkspace { return false } - if existingProfile != nil && existingProfile.AccountID != "" && + if existingProfile != nil && + existingProfile.AccountID == authArguments.AccountID && (existingProfile.WorkspaceID == "" || existingProfile.WorkspaceID == auth.WorkspaceIDNone) { return false } diff --git a/cmd/auth/login_test.go b/cmd/auth/login_test.go index 68e038f40e..78a255fbd8 100644 --- a/cmd/auth/login_test.go +++ b/cmd/auth/login_test.go @@ -584,6 +584,12 @@ func TestShouldPromptWorkspace(t *testing.T) { existingProfile: workspaceProfile, want: true, }, + { + name: "account-only profile for a different account still prompts", + authArguments: auth.AuthArguments{AccountID: "different-account"}, + existingProfile: legacyAccountProfile, + want: true, + }, { name: "skipWorkspace suppresses the prompt", authArguments: auth.AuthArguments{AccountID: "acc"},