From 10e49c7f22f506e17fa60b1d966228e588ef95f2 Mon Sep 17 00:00:00 2001 From: Amolith Date: Fri, 10 Apr 2026 12:06:57 -0600 Subject: [PATCH] Guard root TUI entry against non-interactive env Bare keld with no subcommand now checks isInteractive() before entering the TUI. In non-interactive environments, returns a clear error message directing the user to specify a subcommand instead of crashing with a bubbletea TTY error. Task: td-MY949DM --- cmd/root.go | 5 +++++ cmd/root_noninteractive_test.go | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 cmd/root_noninteractive_test.go diff --git a/cmd/root.go b/cmd/root.go index 9750d9bf6fc1c3b471408996482347a7c195779d..1cae4d1c4c20d50e0e474f5fff1e3b9a759574ad 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -44,6 +44,11 @@ var rootCmd = &cobra.Command{ TraverseChildren: true, RunE: func(cmd *cobra.Command, _ []string) error { + // Bare keld with no subcommand requires a tty. + if !isInteractive() { + return fmt.Errorf("keld: no subcommand specified; non-interactive mode requires a subcommand (e.g. 'keld backup')") + } + commandName, preset, overrides, err := runInteractive("") if err != nil { return err diff --git a/cmd/root_noninteractive_test.go b/cmd/root_noninteractive_test.go new file mode 100644 index 0000000000000000000000000000000000000000..dc2409e22b120d559635870cfb2feaccdb533e63 --- /dev/null +++ b/cmd/root_noninteractive_test.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "strings" + "testing" +) + +func TestRootRunNonInteractiveRequiresSubcommand(t *testing.T) { + // Not parallel: mutates package-level isStdinTerminal. + + // Override stdin terminal check to simulate non-interactive environment. + original := isStdinTerminal + t.Cleanup(func() { + isStdinTerminal = original + }) + isStdinTerminal = func() bool { return false } + + // Reset root command flags to defaults. + setRootFlagValuesForTest(t, "", false, "") + t.Setenv("KELD_CONFIG_FILE", "") + t.Setenv("KELD_DRYRUN", "") + + err := rootCmd.RunE(rootCmd, nil) + if err == nil { + t.Fatal("expected error for bare keld in non-interactive mode, got nil") + } + + errMsg := err.Error() + // Should NOT be a bubbletea TTY error. + if strings.Contains(errMsg, "bubbletea") || strings.Contains(errMsg, "TTY") { + t.Fatalf("non-interactive mode incorrectly tried to open TTY: %v", err) + } + + // Should mention missing subcommand. + if !strings.Contains(errMsg, "subcommand") { + t.Fatalf("expected error to mention subcommand, got: %v", err) + } +} \ No newline at end of file