From 9543ba6398227c07fcb8b62936e90d1860006c6c Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Fri, 18 Sep 2026 14:42:14 +0200 Subject: [PATCH 1/7] Fix incorrect ?w= workspace parameter in resource URLs --- .../fix-jobs-link-w-param-workspace-switch.md | 1 + bundle/config/mutator/initialize_urls.go | 9 ++-- bundle/config/mutator/initialize_urls_test.go | 44 +++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 .nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md diff --git a/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md b/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md new file mode 100644 index 00000000000..81dc525cb12 --- /dev/null +++ b/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md @@ -0,0 +1 @@ +Fix incorrect `?w=` workspace parameter in resource URLs emitted by `bundle summary`. The URL initialiser was using a config fast-path that could return a stale workspace ID from `.databrickscfg` or `databricks.yml`, causing navigation to the wrong workspace when clicking job links. It now calls the workspace API directly so the numeric org ID always matches the connected workspace. diff --git a/bundle/config/mutator/initialize_urls.go b/bundle/config/mutator/initialize_urls.go index c3a877d9e86..d6c62342a6c 100644 --- a/bundle/config/mutator/initialize_urls.go +++ b/bundle/config/mutator/initialize_urls.go @@ -3,10 +3,10 @@ package mutator import ( "context" "net/url" + "strconv" "strings" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/diag" ) @@ -25,12 +25,15 @@ func (m *initializeURLs) Name() string { } func (m *initializeURLs) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - workspaceID, err := auth.ResolveWorkspaceID(ctx, b.WorkspaceClient(ctx)) + // Use CurrentWorkspaceID (API call) rather than the config fast-path so + // that stale or mis-scoped workspace_id values in .databrickscfg or + // databricks.yml never pollute the ?w= query parameter in resource URLs. + workspaceID, err := b.WorkspaceClient(ctx).CurrentWorkspaceID(ctx) if err != nil { return diag.FromErr(err) } host := b.WorkspaceClient(ctx).Config.CanonicalHostName() - err = initializeForWorkspace(b, workspaceID, host) + err = initializeForWorkspace(b, strconv.FormatInt(workspaceID, 10), host) if err != nil { return diag.FromErr(err) } diff --git a/bundle/config/mutator/initialize_urls_test.go b/bundle/config/mutator/initialize_urls_test.go index 16e64b6c420..a2ade939ece 100644 --- a/bundle/config/mutator/initialize_urls_test.go +++ b/bundle/config/mutator/initialize_urls_test.go @@ -6,6 +6,8 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/libs/testserver" + "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/catalog" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/databricks/databricks-sdk-go/service/jobs" @@ -149,3 +151,45 @@ func TestInitializeURLsWithoutOrgId(t *testing.T) { require.Equal(t, "https://adb-123456.azuredatabricks.net/jobs/1", b.Config.Resources.Jobs["job1"].URL) } + +// TestInitializeURLsApplyUsesAPINotConfig verifies that Apply uses +// CurrentWorkspaceID (an API call that returns X-Databricks-Org-Id) rather +// than Config.WorkspaceID. If a stale or mis-scoped workspace_id is present in +// .databrickscfg or databricks.yml, it must not leak into the ?w= parameter. +func TestInitializeURLsApplyUsesAPINotConfig(t *testing.T) { + server := testserver.New(t) + testserver.AddDefaultHandlers(server) + // AddDefaultHandlers registers a /Me handler that returns + // X-Databricks-Org-Id: 900800700600 — the authoritative workspace ID. + + w, err := databricks.NewWorkspaceClient(&databricks.Config{ + Host: server.URL, + Token: "testtoken", + WorkspaceID: "stale-wrong-id", // simulates a mis-scoped config value + }) + require.NoError(t, err) + + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{ + "job1": { + ID: "1", + JobSettings: jobs.JobSettings{Name: "job1"}, + }, + }, + }, + }, + } + b.SetWorkpaceClient(w) + + diags := InitializeURLs().Apply(t.Context(), b) + require.NoError(t, diags.Error()) + + // URL must use the org ID from the API response (900800700600), not the + // stale "stale-wrong-id" from Config.WorkspaceID. + require.Equal(t, + server.URL+"/jobs/1?w=900800700600", + b.Config.Resources.Jobs["job1"].URL, + ) +} From cc457b6c4be460274d394b1f21cc000321874499 Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Fri, 18 Sep 2026 14:45:40 +0200 Subject: [PATCH 2/7] fix changelog --- .nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md b/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md index 81dc525cb12..c265b56c6b7 100644 --- a/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md +++ b/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md @@ -1 +1 @@ -Fix incorrect `?w=` workspace parameter in resource URLs emitted by `bundle summary`. The URL initialiser was using a config fast-path that could return a stale workspace ID from `.databrickscfg` or `databricks.yml`, causing navigation to the wrong workspace when clicking job links. It now calls the workspace API directly so the numeric org ID always matches the connected workspace. +* Fix incorrect `?w=` workspace parameter in resource URLs emitted by `bundle summary`. The URL initialiser was using a config fast-path that could return a stale workspace ID from `.databrickscfg` or `databricks.yml`, causing navigation to the wrong workspace when clicking job links. It now calls the workspace API directly so the numeric org ID always matches the connected workspace. From eb426178f1ed49fd40e1fcc54ca8f6a9c0e33875 Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Fri, 18 Sep 2026 15:52:33 +0200 Subject: [PATCH 3/7] update output --- .../simple/out.requests.summary.direct.json | 12 ++++++++++++ .../simple/out.requests.summary.terraform.json | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/acceptance/bundle/user_agent/simple/out.requests.summary.direct.json b/acceptance/bundle/user_agent/simple/out.requests.summary.direct.json index 76cf7173b4f..9b6f9e31206 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.summary.direct.json +++ b/acceptance/bundle/user_agent/simple/out.requests.summary.direct.json @@ -33,6 +33,18 @@ "return_export_info": "true" } } +{ + "headers": { + "User-Agent": [ + "cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none engine/direct auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me", + "q": { + "excludedAttributes": "entitlements" + } +} { "headers": { "User-Agent": [ diff --git a/acceptance/bundle/user_agent/simple/out.requests.summary.terraform.json b/acceptance/bundle/user_agent/simple/out.requests.summary.terraform.json index 76cf7173b4f..da4127ed7f2 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.summary.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.summary.terraform.json @@ -33,6 +33,18 @@ "return_export_info": "true" } } +{ + "headers": { + "User-Agent": [ + "cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none engine/terraform auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me", + "q": { + "excludedAttributes": "entitlements" + } +} { "headers": { "User-Agent": [ From 1a412f2ea9cb9cea5be1a9a55c7faad20ea5d74f Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Fri, 18 Sep 2026 15:55:24 +0200 Subject: [PATCH 4/7] fix pr link --- .nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md b/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md index c265b56c6b7..28ffb0fd755 100644 --- a/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md +++ b/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md @@ -1 +1 @@ -* Fix incorrect `?w=` workspace parameter in resource URLs emitted by `bundle summary`. The URL initialiser was using a config fast-path that could return a stale workspace ID from `.databrickscfg` or `databricks.yml`, causing navigation to the wrong workspace when clicking job links. It now calls the workspace API directly so the numeric org ID always matches the connected workspace. +* Fix incorrect `?w=` workspace parameter in resource URLs emitted by `bundle summary`. The URL initialiser was using a config fast-path that could return a stale workspace ID from `.databrickscfg` or `databricks.yml`, causing navigation to the wrong workspace when clicking job links. It now calls the workspace API directly so the numeric org ID always matches the connected workspace. ([#6754](https://github.com/databricks/cli/pull/6754)) From 902d8a1780577ee5b0a7e7ae07c3b2d6712714f6 Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Fri, 18 Sep 2026 16:50:49 +0200 Subject: [PATCH 5/7] fix output --- acceptance/bundle/user_agent/output.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/acceptance/bundle/user_agent/output.txt b/acceptance/bundle/user_agent/output.txt index 318ccc3ff3c..ebe0b86e0b7 100644 --- a/acceptance/bundle/user_agent/output.txt +++ b/acceptance/bundle/user_agent/output.txt @@ -125,10 +125,12 @@ MISS run.terraform /.well-known/databricks-config 'cli/[CLI_VERSION] databricks- MISS summary.direct /api/2.0/preview/scim/v2/Me 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' MISS summary.direct /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' MISS summary.direct /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' +OK summary.direct /api/2.0/preview/scim/v2/Me engine/direct MISS summary.direct /.well-known/databricks-config 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' MISS summary.terraform /api/2.0/preview/scim/v2/Me 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' MISS summary.terraform /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' MISS summary.terraform /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' +OK summary.terraform /api/2.0/preview/scim/v2/Me engine/terraform MISS summary.terraform /.well-known/databricks-config 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' MISS validate.direct /api/2.0/preview/scim/v2/Me 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_validate cmd-exec-id/[UUID] interactive/none auth/pat' MISS validate.direct /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_validate cmd-exec-id/[UUID] interactive/none auth/pat' From c8dc9310f6b5ba595c012447f6ea1b5672b8af7f Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Mon, 21 Sep 2026 12:47:25 +0200 Subject: [PATCH 6/7] error on mismatch --- bundle/config/mutator/initialize_urls.go | 34 ++++++++++-- bundle/config/mutator/initialize_urls_test.go | 55 +++++++++++++++---- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/bundle/config/mutator/initialize_urls.go b/bundle/config/mutator/initialize_urls.go index d6c62342a6c..6926356781a 100644 --- a/bundle/config/mutator/initialize_urls.go +++ b/bundle/config/mutator/initialize_urls.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/diag" ) @@ -25,15 +26,38 @@ func (m *initializeURLs) Name() string { } func (m *initializeURLs) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - // Use CurrentWorkspaceID (API call) rather than the config fast-path so - // that stale or mis-scoped workspace_id values in .databrickscfg or - // databricks.yml never pollute the ?w= query parameter in resource URLs. - workspaceID, err := b.WorkspaceClient(ctx).CurrentWorkspaceID(ctx) + // ResolveWorkspaceID returns Config.WorkspaceID when set (fast-path), + // falling back to CurrentWorkspaceID when not. UUID/connection-style IDs + // flow through unchanged so they are preserved in the ?w= parameter. + workspaceID, err := auth.ResolveWorkspaceID(ctx, b.WorkspaceClient(ctx)) if err != nil { return diag.FromErr(err) } + + // Validate numeric workspace IDs against the connected workspace. If + // Config.WorkspaceID is a numeric value that disagrees with the actual org + // ID, the config is stale or mis-scoped and would silently embed the wrong + // ?w= in every resource URL. Non-numeric IDs (UUID connection-style IDs, + // the "none" sentinel) are skipped — they can't be compared with the + // integer org ID returned by the API. + if cfgID := b.WorkspaceClient(ctx).Config.WorkspaceID; cfgID != "" { + if cfgNumeric, parseErr := strconv.ParseInt(cfgID, 10, 64); parseErr == nil { + apiID, apiErr := b.WorkspaceClient(ctx).CurrentWorkspaceID(ctx) + if apiErr != nil { + return diag.FromErr(apiErr) + } + if cfgNumeric != apiID { + return diag.Errorf( + "workspace_id %s in your configuration does not match the connected workspace (ID: %s); "+ + "remove or correct workspace_id in your profile or bundle config to disambiguate", + cfgID, strconv.FormatInt(apiID, 10), + ) + } + } + } + host := b.WorkspaceClient(ctx).Config.CanonicalHostName() - err = initializeForWorkspace(b, strconv.FormatInt(workspaceID, 10), host) + err = initializeForWorkspace(b, workspaceID, host) if err != nil { return diag.FromErr(err) } diff --git a/bundle/config/mutator/initialize_urls_test.go b/bundle/config/mutator/initialize_urls_test.go index a2ade939ece..a1da8856625 100644 --- a/bundle/config/mutator/initialize_urls_test.go +++ b/bundle/config/mutator/initialize_urls_test.go @@ -152,20 +152,18 @@ func TestInitializeURLsWithoutOrgId(t *testing.T) { require.Equal(t, "https://adb-123456.azuredatabricks.net/jobs/1", b.Config.Resources.Jobs["job1"].URL) } -// TestInitializeURLsApplyUsesAPINotConfig verifies that Apply uses -// CurrentWorkspaceID (an API call that returns X-Databricks-Org-Id) rather -// than Config.WorkspaceID. If a stale or mis-scoped workspace_id is present in -// .databrickscfg or databricks.yml, it must not leak into the ?w= parameter. -func TestInitializeURLsApplyUsesAPINotConfig(t *testing.T) { +// TestInitializeURLsApplyNonNumericConfigPassedThrough verifies that a +// non-numeric Config.WorkspaceID (e.g. a UUID connection-style identifier) is +// passed through unchanged into the ?w= parameter. The numeric-mismatch check +// is skipped because such IDs cannot be compared against an integer org ID. +func TestInitializeURLsApplyNonNumericConfigPassedThrough(t *testing.T) { server := testserver.New(t) testserver.AddDefaultHandlers(server) - // AddDefaultHandlers registers a /Me handler that returns - // X-Databricks-Org-Id: 900800700600 — the authoritative workspace ID. w, err := databricks.NewWorkspaceClient(&databricks.Config{ Host: server.URL, Token: "testtoken", - WorkspaceID: "stale-wrong-id", // simulates a mis-scoped config value + WorkspaceID: "some-uuid-style-id", }) require.NoError(t, err) @@ -186,10 +184,45 @@ func TestInitializeURLsApplyUsesAPINotConfig(t *testing.T) { diags := InitializeURLs().Apply(t.Context(), b) require.NoError(t, diags.Error()) - // URL must use the org ID from the API response (900800700600), not the - // stale "stale-wrong-id" from Config.WorkspaceID. + // UUID flows through into ?w= unchanged — no numeric comparison is possible. require.Equal(t, - server.URL+"/jobs/1?w=900800700600", + server.URL+"/jobs/1?w=some-uuid-style-id", b.Config.Resources.Jobs["job1"].URL, ) } + +// TestInitializeURLsApplyErrorsOnNumericWorkspaceIDMismatch verifies that Apply +// returns an error when Config.WorkspaceID is a numeric value that differs from +// the workspace org ID returned by the API. This prevents silently embedding a +// wrong ?w= parameter in resource URLs that would navigate to an unexpected workspace. +func TestInitializeURLsApplyErrorsOnNumericWorkspaceIDMismatch(t *testing.T) { + server := testserver.New(t) + testserver.AddDefaultHandlers(server) + // /Me returns X-Databricks-Org-Id: 900800700600. + + w, err := databricks.NewWorkspaceClient(&databricks.Config{ + Host: server.URL, + Token: "testtoken", + WorkspaceID: "12345", // numeric but does not match 900800700600 + }) + require.NoError(t, err) + + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{ + "job1": { + ID: "1", + JobSettings: jobs.JobSettings{Name: "job1"}, + }, + }, + }, + }, + } + b.SetWorkpaceClient(w) + + diags := InitializeURLs().Apply(t.Context(), b) + require.ErrorContains(t, diags.Error(), "12345") + require.ErrorContains(t, diags.Error(), "900800700600") + require.ErrorContains(t, diags.Error(), "disambiguate") +} From 791ce82d0aab2edba2f5e87e4305afe287d0f5c5 Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Mon, 21 Sep 2026 13:35:09 +0200 Subject: [PATCH 7/7] update nextchangelog --- .nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md b/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md index 28ffb0fd755..1b8c6ef384d 100644 --- a/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md +++ b/.nextchanges/bundles/fix-jobs-link-w-param-workspace-switch.md @@ -1 +1 @@ -* Fix incorrect `?w=` workspace parameter in resource URLs emitted by `bundle summary`. The URL initialiser was using a config fast-path that could return a stale workspace ID from `.databrickscfg` or `databricks.yml`, causing navigation to the wrong workspace when clicking job links. It now calls the workspace API directly so the numeric org ID always matches the connected workspace. ([#6754](https://github.com/databricks/cli/pull/6754)) +* Fix incorrect `?w=` workspace parameter in resource URLs emitted by `bundle summary`. When a numeric `workspace_id` is explicitly set in `.databrickscfg` or `databricks.yml` and it does not match the connected workspace's org ID, the CLI now returns an error asking the user to remove or correct the value. This prevents stale or mis-scoped workspace IDs from silently embedding the wrong `?w=` in job and pipeline links and causing unexpected workspace switches. ([#6754](https://github.com/databricks/cli/pull/6754))