From 26a3dca334867f4af8bcb5bdc030271724f5175e Mon Sep 17 00:00:00 2001 From: Tynan Daly Date: Mon, 11 May 2026 19:14:33 -0700 Subject: [PATCH] fix(stderr): route diagnostics to stderr, keep stdout for data `vers run --json` previously emitted 'Warning: vers.toml not found...' to stdout, contaminating the JSON output and breaking `vers run --json | jq`. Same shape elsewhere \u2014 six diagnostics across runconfig, presenters, and handlers were using `fmt.Println`/`Printf` (stdout) instead of stderr. Fixed: - internal/runconfig/config.go 'vers.toml not found' warning - internal/presenters/run_presenter.go '.vers directory not found' warning - internal/presenters/branch_presenter.go 'no VM IDs returned' error + HEAD update warning - internal/handlers/upgrade.go 'skipping checksum verification' warning - internal/handlers/kill.go 'about to delete VM' confirmation header After this, `vers run --json 2>/dev/null` produces clean JSON that `jq` can parse. Addresses agent-native CLI principle 2 (data on stdout, diagnostics on stderr). --- internal/handlers/kill.go | 3 ++- internal/handlers/upgrade.go | 2 +- internal/presenters/branch_presenter.go | 5 +++-- internal/presenters/run_presenter.go | 3 ++- internal/runconfig/config.go | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/handlers/kill.go b/internal/handlers/kill.go index a83be97..774b8c8 100644 --- a/internal/handlers/kill.go +++ b/internal/handlers/kill.go @@ -3,6 +3,7 @@ package handlers import ( "context" "fmt" + "os" "github.com/hdresearch/vers-cli/internal/app" "github.com/hdresearch/vers-cli/internal/presenters" @@ -30,7 +31,7 @@ func HandleKill(ctx context.Context, a *app.App, r KillReq) error { // Confirm if needed if !r.SkipConfirmation { for _, t := range targets { - fmt.Printf("Warning: You are about to delete VM '%s'\n", t) + fmt.Fprintf(os.Stderr, "Warning: You are about to delete VM '%s'\n", t) } ok, _ := a.Prompter.YesNo("Proceed") if !ok { diff --git a/internal/handlers/upgrade.go b/internal/handlers/upgrade.go index 5ff7424..3417df0 100644 --- a/internal/handlers/upgrade.go +++ b/internal/handlers/upgrade.go @@ -107,7 +107,7 @@ func performUpgrade(DebugPrint func(string, ...any), release *update.GitHubRelea } fmt.Println("Checksum verification passed") } else if skipChecksum { - fmt.Println("warning: skipping checksum verification (not recommended)") + fmt.Fprintln(os.Stderr, "warning: skipping checksum verification (not recommended)") } currentExe, err := os.Executable() diff --git a/internal/presenters/branch_presenter.go b/internal/presenters/branch_presenter.go index c7e02b4..6c1c4ec 100644 --- a/internal/presenters/branch_presenter.go +++ b/internal/presenters/branch_presenter.go @@ -2,6 +2,7 @@ package presenters import ( "fmt" + "os" "github.com/hdresearch/vers-cli/internal/app" ) @@ -12,7 +13,7 @@ func RenderBranch(a *app.App, res BranchView) { newIDs = []string{res.NewID} } if len(newIDs) == 0 { - fmt.Println("Error: no VM IDs returned from branch operation") + fmt.Fprintln(os.Stderr, "Error: no VM IDs returned from branch operation") return } numNew := len(newIDs) @@ -50,7 +51,7 @@ func RenderBranch(a *app.App, res BranchView) { } if res.CheckoutErr != nil { - fmt.Printf("WARNING: Failed to update HEAD: %v\n", res.CheckoutErr) + fmt.Fprintf(os.Stderr, "WARNING: Failed to update HEAD: %v\n", res.CheckoutErr) return } diff --git a/internal/presenters/run_presenter.go b/internal/presenters/run_presenter.go index d05d081..113607d 100644 --- a/internal/presenters/run_presenter.go +++ b/internal/presenters/run_presenter.go @@ -2,6 +2,7 @@ package presenters import ( "fmt" + "os" "github.com/hdresearch/vers-cli/internal/app" ) @@ -18,6 +19,6 @@ func RenderRun(a *app.App, v RunView) { if v.HeadTarget != "" { fmt.Printf("HEAD now points to: %s\n", v.HeadTarget) } else { - fmt.Println("Warning: .vers directory not found. Run 'vers init' first.") + fmt.Fprintln(os.Stderr, "Warning: .vers directory not found. Run 'vers init' first.") } } diff --git a/internal/runconfig/config.go b/internal/runconfig/config.go index efc58ff..6dceb6e 100644 --- a/internal/runconfig/config.go +++ b/internal/runconfig/config.go @@ -55,7 +55,7 @@ func Default() *Config { func Load() (*Config, error) { cfg := Default() if _, err := os.Stat("vers.toml"); os.IsNotExist(err) { - fmt.Println("Warning: vers.toml not found, using default configuration") + fmt.Fprintln(os.Stderr, "Warning: vers.toml not found, using default configuration") return cfg, nil } if _, err := toml.DecodeFile("vers.toml", cfg); err != nil {