From 74d1d1966a05ba24cfa53a305608bff1fc10189a Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 23 Mar 2026 17:10:26 +0000 Subject: [PATCH 1/5] fix: improve `apps deploy` error message when APP_NAME is missing The old error "accepts 1 arg(s), received 0" was unclear. The new message explains that APP_NAME is required, shows usage, mentions the databricks.yml alternative, and suggests an app name inferred from the current directory. Co-authored-by: Isaac Signed-off-by: James Broadhead --- cmd/apps/bundle_helpers.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/cmd/apps/bundle_helpers.go b/cmd/apps/bundle_helpers.go index 6b1712de56..1a408a49a8 100644 --- a/cmd/apps/bundle_helpers.go +++ b/cmd/apps/bundle_helpers.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "os" + "path/filepath" "strings" "time" @@ -27,12 +29,41 @@ func makeArgsOptionalWithBundle(cmd *cobra.Command, usage string) { return fmt.Errorf("accepts at most 1 arg(s), received %d", len(args)) } if !hasBundleConfig() && len(args) != 1 { - return fmt.Errorf("accepts 1 arg(s), received %d", len(args)) + return missingAppNameError() } return nil } } +// missingAppNameError returns an error message that explains what the positional +// argument should be, and attempts to infer a suggestion from the local environment. +func missingAppNameError() error { + hint := inferAppNameHint() + msg := `missing required argument: APP_NAME + +Usage: databricks apps APP_NAME + +APP_NAME is the name of the Databricks app to operate on. +Alternatively, run this command from a project directory containing +databricks.yml to auto-detect the app name.` + + if hint != "" { + msg += fmt.Sprintf("\n\nDid you mean?\n databricks apps deploy %s", hint) + } + + return errors.New(msg) +} + +// inferAppNameHint tries to suggest an app name from the local environment. +// It checks the current directory name as a best-effort hint. +func inferAppNameHint() string { + wd, err := os.Getwd() + if err != nil { + return "" + } + return filepath.Base(wd) +} + // getAppNameFromArgs returns the app name from args or detects it from the bundle. // Returns (appName, fromBundle, error). func getAppNameFromArgs(cmd *cobra.Command, args []string) (string, bool, error) { From a57135b2fd3e5476efc799589a094f63304b52db Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 23 Mar 2026 17:20:09 +0000 Subject: [PATCH 2/5] fix: only show app name hint when cwd contains app.yml/app.yaml Co-authored-by: Isaac Signed-off-by: James Broadhead --- cmd/apps/bundle_helpers.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmd/apps/bundle_helpers.go b/cmd/apps/bundle_helpers.go index 1a408a49a8..4764ccd3b8 100644 --- a/cmd/apps/bundle_helpers.go +++ b/cmd/apps/bundle_helpers.go @@ -55,13 +55,21 @@ databricks.yml to auto-detect the app name.` } // inferAppNameHint tries to suggest an app name from the local environment. -// It checks the current directory name as a best-effort hint. +// Only returns a hint if the current directory looks like a Databricks app +// (contains app.yml or app.yaml), using the directory name as the suggestion. func inferAppNameHint() string { wd, err := os.Getwd() if err != nil { return "" } - return filepath.Base(wd) + + for _, filename := range []string{"app.yml", "app.yaml"} { + if _, err := os.Stat(filepath.Join(wd, filename)); err == nil { + return filepath.Base(wd) + } + } + + return "" } // getAppNameFromArgs returns the app name from args or detects it from the bundle. From 2cc32a10fce16679273c52c89f55d8e9e263cfe3 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 23 Mar 2026 17:23:33 +0000 Subject: [PATCH 3/5] test: add tests for inferAppNameHint and missingAppNameError Cover normal cases (app.yml, app.yaml, no config) and edge cases (deleted cwd) to verify graceful degradation. Co-authored-by: Isaac Signed-off-by: James Broadhead --- cmd/apps/bundle_helpers_test.go | 76 +++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/cmd/apps/bundle_helpers_test.go b/cmd/apps/bundle_helpers_test.go index 359d365f86..96c458538a 100644 --- a/cmd/apps/bundle_helpers_test.go +++ b/cmd/apps/bundle_helpers_test.go @@ -3,6 +3,8 @@ package apps import ( "context" "errors" + "os" + "path/filepath" "testing" "github.com/databricks/databricks-sdk-go/service/apps" @@ -106,6 +108,80 @@ func TestFormatAppStatusMessage(t *testing.T) { }) } +func TestInferAppNameHint(t *testing.T) { + t.Run("returns empty when no app config exists", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + + assert.Equal(t, "", inferAppNameHint()) + }) + + t.Run("returns dir name when app.yml exists", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + + assert.Equal(t, filepath.Base(dir), inferAppNameHint()) + }) + + t.Run("returns dir name when app.yaml exists", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("command: [\"python\"]"), 0o644) + + assert.Equal(t, filepath.Base(dir), inferAppNameHint()) + }) + + t.Run("returns empty when cwd has been deleted", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + os.Remove(dir) + + assert.Equal(t, "", inferAppNameHint()) + }) +} + +func TestMissingAppNameError(t *testing.T) { + t.Run("includes APP_NAME and usage info", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + + err := missingAppNameError() + assert.Contains(t, err.Error(), "APP_NAME") + assert.Contains(t, err.Error(), "databricks.yml") + assert.NotContains(t, err.Error(), "Did you mean") + }) + + t.Run("includes hint when app.yml exists", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + + err := missingAppNameError() + assert.Contains(t, err.Error(), "Did you mean") + assert.Contains(t, err.Error(), filepath.Base(dir)) + }) + + t.Run("gracefully handles deleted cwd", func(t *testing.T) { + dir := t.TempDir() + testChdir(t, dir) + os.Remove(dir) + + err := missingAppNameError() + assert.Contains(t, err.Error(), "APP_NAME") + assert.NotContains(t, err.Error(), "Did you mean") + }) +} + +// testChdir changes to the given directory for the duration of the test. +func testChdir(t *testing.T, dir string) { + t.Helper() + orig, err := os.Getwd() + assert.NoError(t, err) + assert.NoError(t, os.Chdir(dir)) + t.Cleanup(func() { os.Chdir(orig) }) +} + func TestMakeArgsOptionalWithBundle(t *testing.T) { t.Run("updates command usage", func(t *testing.T) { cmd := &cobra.Command{} From 89a20876dc7f54b1caf820dcdf4449fe02329692 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 23 Mar 2026 17:24:02 +0000 Subject: [PATCH 4/5] style: replace fmt.Sprintf with string concatenation Co-authored-by: Isaac Signed-off-by: James Broadhead --- cmd/apps/bundle_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/apps/bundle_helpers.go b/cmd/apps/bundle_helpers.go index 4764ccd3b8..04992c592e 100644 --- a/cmd/apps/bundle_helpers.go +++ b/cmd/apps/bundle_helpers.go @@ -48,7 +48,7 @@ Alternatively, run this command from a project directory containing databricks.yml to auto-detect the app name.` if hint != "" { - msg += fmt.Sprintf("\n\nDid you mean?\n databricks apps deploy %s", hint) + msg += "\n\nDid you mean?\n databricks apps deploy " + hint } return errors.New(msg) From 300741bd34ea0d67517e6a7aca6992187f807ece Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 23 Mar 2026 17:49:45 +0000 Subject: [PATCH 5/5] fix: address lint issues in tests - Check os.WriteFile error return values - Use t.Chdir() instead of manual os.Chdir + cleanup - Remove unnecessary testChdir helper Co-authored-by: Isaac Signed-off-by: James Broadhead --- cmd/apps/bundle_helpers_test.go | 34 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/cmd/apps/bundle_helpers_test.go b/cmd/apps/bundle_helpers_test.go index 96c458538a..0a9a9526b3 100644 --- a/cmd/apps/bundle_helpers_test.go +++ b/cmd/apps/bundle_helpers_test.go @@ -110,31 +110,32 @@ func TestFormatAppStatusMessage(t *testing.T) { func TestInferAppNameHint(t *testing.T) { t.Run("returns empty when no app config exists", func(t *testing.T) { - dir := t.TempDir() - testChdir(t, dir) + t.Chdir(t.TempDir()) assert.Equal(t, "", inferAppNameHint()) }) t.Run("returns dir name when app.yml exists", func(t *testing.T) { dir := t.TempDir() - testChdir(t, dir) - os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + t.Chdir(dir) + err := os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, err) assert.Equal(t, filepath.Base(dir), inferAppNameHint()) }) t.Run("returns dir name when app.yaml exists", func(t *testing.T) { dir := t.TempDir() - testChdir(t, dir) - os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("command: [\"python\"]"), 0o644) + t.Chdir(dir) + err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, err) assert.Equal(t, filepath.Base(dir), inferAppNameHint()) }) t.Run("returns empty when cwd has been deleted", func(t *testing.T) { dir := t.TempDir() - testChdir(t, dir) + t.Chdir(dir) os.Remove(dir) assert.Equal(t, "", inferAppNameHint()) @@ -143,8 +144,7 @@ func TestInferAppNameHint(t *testing.T) { func TestMissingAppNameError(t *testing.T) { t.Run("includes APP_NAME and usage info", func(t *testing.T) { - dir := t.TempDir() - testChdir(t, dir) + t.Chdir(t.TempDir()) err := missingAppNameError() assert.Contains(t, err.Error(), "APP_NAME") @@ -154,8 +154,9 @@ func TestMissingAppNameError(t *testing.T) { t.Run("includes hint when app.yml exists", func(t *testing.T) { dir := t.TempDir() - testChdir(t, dir) - os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + t.Chdir(dir) + writeErr := os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, writeErr) err := missingAppNameError() assert.Contains(t, err.Error(), "Did you mean") @@ -164,7 +165,7 @@ func TestMissingAppNameError(t *testing.T) { t.Run("gracefully handles deleted cwd", func(t *testing.T) { dir := t.TempDir() - testChdir(t, dir) + t.Chdir(dir) os.Remove(dir) err := missingAppNameError() @@ -173,15 +174,6 @@ func TestMissingAppNameError(t *testing.T) { }) } -// testChdir changes to the given directory for the duration of the test. -func testChdir(t *testing.T, dir string) { - t.Helper() - orig, err := os.Getwd() - assert.NoError(t, err) - assert.NoError(t, os.Chdir(dir)) - t.Cleanup(func() { os.Chdir(orig) }) -} - func TestMakeArgsOptionalWithBundle(t *testing.T) { t.Run("updates command usage", func(t *testing.T) { cmd := &cobra.Command{}