Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changesets/1790084751-8116f03c.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
id: 1790084751-8116f03c
features:
- core
targets:
- cli
type: fix
bump: patch
description: planned commands report CLI_UNAVAILABLE for arbitrary positional arguments and unknown flags
author: 2ynn
date: "2026-09-22"
7 changes: 4 additions & 3 deletions templates/templates/cli/intents.go.stmpl
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ func initIntentCmds(rootCmd *cobra.Command) error {

func newPlannedCmd(name, short, long, groupID, note string) *cobra.Command {
cmd := &cobra.Command{
Use: name,
Short: short,
Args: cobra.NoArgs,
Use: name,
Short: short,
Args: cobra.ArbitraryArgs,
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
RunE: func(cmd *cobra.Command, args []string) error {
if usage.UsageRequested(cmd) {
return usage.EmitSchema(cmd, cmd.OutOrStdout())
Expand Down
26 changes: 26 additions & 0 deletions templates/templates/cli/tests/primary/clierrors_test.go.stmpl
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,32 @@ func TestCLIErrorAgentModeMatrix(t *testing.T) {
}
}

func TestCLIErrorPlannedCommandIgnoresArgv(t *testing.T) {
for _, args := range [][]string{
{"--agent-mode", "archive"},
{"--agent-mode", "archive", "hello world"},
{"--agent-mode", "archive", "--count", "3"},
{"--agent-mode", "archive", "hello", "--count", "3"},
} {
t.Run(strings.Join(args[1:], " "), func(t *testing.T) {
resetAgentMode(t)
h := NewCLITestHarness(t)
err := h.RunRaw(args)
require.Error(t, err)
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
assert.True(t, output.IsRendered(err), "a CLI error rendered in the envelope must be marked so main prints nothing further")

envelope := agentEnvelope(t, h.GetStderr())
assert.Equal(t, "unsupported_error", envelope["error_type"])
assert.Equal(t, "CLI_UNAVAILABLE", envelope["error_reason"])
assert.Equal(t, float64(1), envelope["exit_code"])
assert.NotContains(t, envelope["error"], "unknown command")
assert.NotContains(t, envelope["error"], "unknown flag")
assert.NotContains(t, h.GetStderr(), "--dry-run")
assert.Empty(t, strings.TrimSpace(h.GetStdout()))
})
}
}

// TestCLIErrorFlagValuesAreNotRenderingFlags: a flag-looking VALUE of a real
// flag must not switch the rendering mode: `--body --agent-mode` is a body of
// "--agent-mode" (invalid JSON, plain error), not agent mode; `--body -o json`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ func TestExitCodesAcrossRenderingModes(t *testing.T) {
{name: "missing required flag", args: []string{"custom-required-flags"}, want: 2},
{name: "malformed body", args: []string{"request-bodies", "request-body-post-flag-defaults-body-merge", "--body", "{"}, want: 2},
{name: "planned command", args: []string{"archive"}, want: 1},
{name: "planned command with argument", args: []string{"archive", "hello world"}, want: 1},
{name: "planned command with unknown flag", args: []string{"archive", "--count", "3"}, want: 1},
{name: "builtin stray argument", args: []string{"version", "extra"}, want: 2},
{name: "HTTP 400", args: []string{"errors", "status-get-error", "--status-code", "400"}, serverURL: s.URL, want: 2},
{name: "HTTP 401", args: []string{"errors", "status-get-error", "--status-code", "401"}, serverURL: s.URL, want: 3},
Expand Down
20 changes: 20 additions & 0 deletions templates/templates/cli/usage_test.go.stmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
{{- addImport "github.com/spf13/pflag" -}}
{{- addImport "internal/cli" true -}}
{{- end -}}
{{- if templatePlannedUsageTestEnabled -}}
{{- addImport "internal/clierrors" true -}}
{{- end -}}
package tests

{{genImports}}
Expand Down Expand Up @@ -311,6 +314,23 @@ func TestUsageSchema_PlannedCommand(t *testing.T) {
assert.Contains(t, stdout, "cmd \"{{templatePlannedUsageCmdName}}\"")
assert.Empty(t, stderr)
}

func TestPlannedCommand_ArgvReportsUnavailable(t *testing.T) {
for _, extra := range [][]string{
{"hello"},
{"--count", "3"},
} {
t.Run(strings.Join(extra, " "), func(t *testing.T) {
h := NewCLITestHarness(t)
err := h.RunBare(append([]string{ {{templatePlannedUsagePathArgs}} }, extra...))
require.Error(t, err)

assert.Equal(t, clierrors.ExitRuntime, clierrors.ExitCode(err))
assert.Contains(t, h.GetStderr(), `"error_reason": "CLI_UNAVAILABLE"`)
assert.Empty(t, strings.TrimSpace(h.GetStdout()))
})
}
}
{{- end }}

{{- if $custom }}
Expand Down
4 changes: 2 additions & 2 deletions zSDKs/sdk-cli/.speakeasy/gen.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions zSDKs/sdk-cli/internal/cli/intents.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ func initIntentCmds(rootCmd *cobra.Command) error {

func newPlannedCmd(name, short, long, groupID, note string) *cobra.Command {
cmd := &cobra.Command{
Use: name,
Short: short,
Args: cobra.NoArgs,
Use: name,
Short: short,
Args: cobra.ArbitraryArgs,
Comment thread
2ynn marked this conversation as resolved.
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
RunE: func(cmd *cobra.Command, args []string) error {
if usage.UsageRequested(cmd) {
return usage.EmitSchema(cmd, cmd.OutOrStdout())
Expand Down
18 changes: 18 additions & 0 deletions zSDKs/sdk-cli/tests/usage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"openapi/internal/cli"
"openapi/internal/clierrors"
"regexp"
"strings"
"testing"
Expand Down Expand Up @@ -282,6 +283,23 @@ func TestUsageSchema_PlannedCommand(t *testing.T) {
assert.Empty(t, stderr)
}

func TestPlannedCommand_ArgvReportsUnavailable(t *testing.T) {
for _, extra := range [][]string{
{"hello"},
{"--count", "3"},
} {
t.Run(strings.Join(extra, " "), func(t *testing.T) {
h := NewCLITestHarness(t)
err := h.RunBare(append([]string{"archive"}, extra...))
require.Error(t, err)

assert.Equal(t, clierrors.ExitRuntime, clierrors.ExitCode(err))
assert.Contains(t, h.GetStderr(), `"error_reason": "CLI_UNAVAILABLE"`)
assert.Empty(t, strings.TrimSpace(h.GetStdout()))
})
}
}

func TestUsageSchema_CustomCommand(t *testing.T) {
h := NewCLITestHarness(t)
err := h.RunBare([]string{"hello", "--usage"})
Expand Down
Loading