From 28787b5ed56e7176c29816547ebc92a5bddcf958 Mon Sep 17 00:00:00 2001 From: dimk0 <4886215+dimk0@users.noreply.github.com> Date: Sat, 14 Nov 2026 23:49:25 +0000 Subject: [PATCH 1/2] air: suggest a working --output-dir when convert-to-dabs code is outside the bundle When code_source.root_path resolved outside the bundle directory, for example the getting-started Ray recipes that snapshot their parent directory with root_path: .., convert-to-dabs suggested setting sync.paths in a databricks.yml that it had not written. The error now names the resolved code directory and, for working-tree snapshots, the --output-dir value that converts the workload. Co-authored-by: Isaac --- cmd/air/convert_to_dabs.go | 8 +++++- cmd/air/convert_to_dabs_test.go | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/cmd/air/convert_to_dabs.go b/cmd/air/convert_to_dabs.go index 363bc07e4d7..48e114a24d4 100644 --- a/cmd/air/convert_to_dabs.go +++ b/cmd/air/convert_to_dabs.go @@ -264,7 +264,13 @@ func bundleCodeSourcePath(ctx context.Context, cfg *runConfig, configPath, bundl } rel, err := filepath.Rel(bundleAbs, root) if err != nil || !filepath.IsLocal(rel) { - return "", fmt.Errorf("code_source root_path %q is not inside the bundle directory %q; bundle deploy can only upload files under the bundle's sync root. Move the code inside the bundle, or set sync.paths in databricks.yml to a directory that contains both (note that widens what gets synced)", snap.RootPath, bundleDir) + remedy := "move the code inside the bundle directory" + // As in codeArtifactFor, only a working-tree snapshot can be bundled from a + // parent directory of the code. + if snap.Git == nil { + remedy = fmt.Sprintf("pass --output-dir %s to write the bundle to the code directory's parent, or %s", filepath.Dir(root), remedy) + } + return "", fmt.Errorf("code_source root_path %q (%s) is not inside the bundle directory %q, and bundle deploy only packages code inside the bundle; %s", snap.RootPath, root, bundleDir, remedy) } return localBundlePath(filepath.ToSlash(rel)), nil } diff --git a/cmd/air/convert_to_dabs_test.go b/cmd/air/convert_to_dabs_test.go index 1ac0a4f8c3c..a0d27393979 100644 --- a/cmd/air/convert_to_dabs_test.go +++ b/cmd/air/convert_to_dabs_test.go @@ -729,3 +729,49 @@ func TestConvertToDabsRejectsUnsupported(t *testing.T) { require.ErrorContains(t, err, "usage_policy_name is not yet supported") }) } + +// The getting-started Ray layout: the workload in ray/core snapshots its parent so it +// can include the shared ray/ray_bootstrap.sh. The error names an --output-dir that +// converts, and does not suggest editing a databricks.yml that was never written. +func TestConvertToDabsCodeOutsideBundleSuggestsOutputDir(t *testing.T) { + dir := t.TempDir() + recipe := filepath.Join(dir, "ray", "core") + require.NoError(t, os.MkdirAll(recipe, 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "ray", "ray_bootstrap.sh"), []byte("#!/bin/bash\n"), 0o600)) + cfg := "experiment_name: ray-core\ncommand: bash ray_bootstrap.sh\n" + + "compute: {accelerator_type: GPU_1xA10, num_accelerators: 2}\n" + + "code_source:\n root_path: ..\n include_paths: [ray_bootstrap.sh, core]\n" + path := filepath.Join(recipe, "workload.yaml") + require.NoError(t, os.WriteFile(path, []byte(cfg), 0o600)) + loaded, err := loadRunConfig(path) + require.NoError(t, err) + + _, _, err = convertToDabs(t.Context(), loaded, path, recipe) + require.ErrorContains(t, err, "not inside the bundle") + assert.Contains(t, err.Error(), "--output-dir "+dir+" ") + assert.NotContains(t, err.Error(), "sync.paths") + + root, _, err := convertToDabs(t.Context(), loaded, path, dir) + require.NoError(t, err) + inc := get(t, root, "artifacts."+codeSourceArtifactKey+".include").MustSequence() + require.Len(t, inc, 2) + assert.Equal(t, "ray/ray_bootstrap.sh", inc[0].MustString()) + assert.Equal(t, "ray/core", inc[1].MustString()) +} + +// A git-pinned snapshot is archived from the bundle root, so a parent directory is +// not offered as the bundle root. +func TestConvertToDabsGitPinOutsideBundleDoesNotSuggestOutputDir(t *testing.T) { + outside := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(outside, "src"), 0o700)) + cfg := "experiment_name: outside\ncommand: python train.py\n" + + "compute: {accelerator_type: GPU_1xH100, num_accelerators: 1}\n" + + "code_source:\n root_path: " + filepath.Join(outside, "src") + "\n git:\n commit: abc123\n" + path := writeConfigFile(t, "run.yaml", cfg) + loaded, err := loadRunConfig(path) + require.NoError(t, err) + + _, _, err = convertToDabs(t.Context(), loaded, path, filepath.Dir(path)) + require.ErrorContains(t, err, "not inside the bundle") + assert.NotContains(t, err.Error(), "--output-dir") +} From 54a6caddabc2ac1e7459b924b200c262632366ba Mon Sep 17 00:00:00 2001 From: dimk0 <4886215+dimk0@users.noreply.github.com> Date: Sun, 15 Nov 2026 00:03:56 +0000 Subject: [PATCH 2/2] Add changelog entry Co-authored-by: Isaac --- .nextchanges/air/7640.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nextchanges/air/7640.md diff --git a/.nextchanges/air/7640.md b/.nextchanges/air/7640.md new file mode 100644 index 00000000000..b75ce0ec8e5 --- /dev/null +++ b/.nextchanges/air/7640.md @@ -0,0 +1 @@ +* `air convert-to-dabs` now suggests the `--output-dir` that converts a workload whose `code_source.root_path` is outside the bundle directory, instead of suggesting `sync.paths`. ([#7640](https://github.com/databricks/cli/pull/7640))