From d7bb1af12af9cfb9721b218388f17caea7682cf7 Mon Sep 17 00:00:00 2001 From: nullkey Date: Thu, 14 May 2026 19:06:38 +0800 Subject: [PATCH] fix(create): exit 5 on InitTask insufficient_credits (0.5.1) InitTask's insufficient_credits handler returned a plain fmt.Errorf which cobra mapped to its default exit 1, while the same condition surfacing later in the stream path explicitly os.Exit(5). The documented contract (AGENTS.md, README) is 5 = business failure; agents branching on exit codes had to know about two paths. Caught during real-backend smoke for 0.5.0 (depleted credits during testing made the inconsistency observable). Match the stream-side pattern verbatim: print the i18n message to stderr, os.Exit(5). Add an integration test that mocks /v1/tasks/init returning envelope code 100001 and asserts the binary exits 5. --- CHANGELOG.md | 12 +++++ cmd/create.go | 4 +- package.json | 2 +- skills/vibeknow-core/SKILL.md | 2 +- skills/vibeknow-create/SKILL.md | 2 +- skills/vibeknow-doc/SKILL.md | 2 +- tests/integration/create_credits_test.go | 60 ++++++++++++++++++++++++ 7 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 tests/integration/create_credits_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d1b403..956c7d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 0.5.1 — 2026-05-14 + +### Fixed + +- `vk create` now exits **5** (business failure) when the backend rejects + `POST /v1/tasks/init` with `insufficient_credits` (envelope code 100001), + matching the stream-side path's existing behavior. Previously this case + exited 1 (cobra's generic error code), inconsistent with the documented + exit-code contract and with the same condition surfacing later in the + pipeline. Caught while running real-backend smoke tests during 0.5.0 + validation. + ## 0.5.0 — 2026-05-14 ### New diff --git a/cmd/create.go b/cmd/create.go index 340a46c..4dc016b 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -149,7 +149,9 @@ var createCmd = &cobra.Command{ task, err := fc.InitTask(ctx, initParams) if err != nil { if errs.HasCode(err, "insufficient_credits") { - return fmt.Errorf("%s", i18n.T("credits.insufficient")) + // Mirror the stream-side path's exit code: business failure → 5. + fmt.Fprintln(os.Stderr, i18n.T("credits.insufficient")) + os.Exit(5) } if errs.HasCode(err, "script_invalid") { // Backend's localized message already lives on the error. diff --git a/package.json b/package.json index 4b9624b..3791223 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vibeknow-cli", - "version": "0.5.0", + "version": "0.5.1", "description": "VibeKnow CLI — turn docs / URLs into videos", "license": "MIT", "bin": { diff --git a/skills/vibeknow-core/SKILL.md b/skills/vibeknow-core/SKILL.md index 587f706..7f70ca7 100644 --- a/skills/vibeknow-core/SKILL.md +++ b/skills/vibeknow-core/SKILL.md @@ -1,6 +1,6 @@ --- name: vibeknow-core -version: 0.5.0 +version: 0.5.1 description: "vibeknow CLI setup, authentication, profile management, and diagnostics. Use when: first-time setup, auth errors, switching environments, diagnosing connection issues." metadata: requires: diff --git a/skills/vibeknow-create/SKILL.md b/skills/vibeknow-create/SKILL.md index 5b1451d..e7a943c 100644 --- a/skills/vibeknow-create/SKILL.md +++ b/skills/vibeknow-create/SKILL.md @@ -1,6 +1,6 @@ --- name: vibeknow-create -version: 0.5.0 +version: 0.5.1 description: "Generate videos from documents/URLs/files, track video task progress, download results, list voice templates. Use when: user wants to create a video, check task status, download video, or browse voices." metadata: requires: diff --git a/skills/vibeknow-doc/SKILL.md b/skills/vibeknow-doc/SKILL.md index b8ee7e3..e3217ce 100644 --- a/skills/vibeknow-doc/SKILL.md +++ b/skills/vibeknow-doc/SKILL.md @@ -1,6 +1,6 @@ --- name: vibeknow-doc -version: 0.5.0 +version: 0.5.1 description: "Upload documents to vectoria and check processing status. Use when: user wants to upload a document, check if a document is ready, or get a doc_id for use with vibeknow create." metadata: requires: diff --git a/tests/integration/create_credits_test.go b/tests/integration/create_credits_test.go new file mode 100644 index 0000000..80e4068 --- /dev/null +++ b/tests/integration/create_credits_test.go @@ -0,0 +1,60 @@ +package integration + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "strings" + "testing" +) + +// TestCreate_InsufficientCreditsOnInit_Exits5 covers the bug fixed in 0.5.1: +// when the backend rejects InitTask with envelope code 100001 (insufficient +// credits), the CLI must exit 5 (business failure) to match the stream-side +// path's behavior, not exit 1 from cobra's default error handler. +func TestCreate_InsufficientCreditsOnInit_Exits5(t *testing.T) { + if testing.Short() { + t.Skip("integration test") + } + + mux := http.NewServeMux() + mux.HandleFunc("/v1/tasks/init", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusPaymentRequired) // 402, matches backend + _ = json.NewEncoder(w).Encode(map[string]any{ + "code": 100001, + "message": "insufficient credits", + }) + }) + srv := httptest.NewServer(mux) + defer srv.Close() + + bin := build(t) + configHome := buildVideoProfile(t, srv.URL) + + cmd := exec.Command(bin, "create", "--from", "doc_abc12345") + var stdout, stderr strings.Builder + cmd.Stdout = &stdout + cmd.Stderr = &stderr + cmd.Env = append(os.Environ(), + "VIBEKNOW_TOKEN=fake-token", + "VIBEKNOW_CONFIG_HOME="+configHome, + ) + + err := cmd.Run() + code := 0 + if ee, ok := err.(*exec.ExitError); ok { + code = ee.ExitCode() + } else if err != nil { + t.Fatalf("run: %v\nstderr: %s", err, stderr.String()) + } + + if code != 5 { + t.Fatalf("exit code = %d, want 5 (business failure)\nstderr: %s", code, stderr.String()) + } + if !strings.Contains(stderr.String(), "insufficient credits") { + t.Fatalf("stderr missing insufficient-credits message:\n%s", stderr.String()) + } +}