Skip to content

Commit 459a9f2

Browse files
somanshreddyclaude
andcommitted
cmd: preserve failure response in --wait, fix formatting
- Add ErrPollFailed sentinel that carries the full status response, so users see error details without a second API call - Builder outputs failure response to stdout then returns exit 1 (same pattern as ErrPaginationTruncated) - --wait success passes nil columns (single object renders as key-value in --human, matching the get command) - Remove stray blank line in root.go Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c29122d commit 459a9f2

5 files changed

Lines changed: 44 additions & 8 deletions

File tree

cmd/heygen/builder.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,22 @@ func buildCobraCommand(spec *command.Spec, ctx *cmdContext) *cobra.Command {
101101
},
102102
})
103103
if err != nil {
104+
var failErr *client.ErrPollFailed
105+
if errors.As(err, &failErr) {
106+
// Output the failure response (contains error details),
107+
// then signal failure via CLIError for exit code 1.
108+
if fmtErr := ctx.formatter.Data(failErr.Data, spec.DataField, nil); fmtErr != nil {
109+
return fmtErr
110+
}
111+
return clierrors.New(failErr.Error())
112+
}
104113
return err
105114
}
106115

107-
return ctx.formatter.Data(result, spec.DataField, defaultColumnsForSpec(spec))
116+
// --wait result is a single resource from the GET endpoint.
117+
// Columns are nil — HumanFormatter renders single objects as
118+
// key-value pairs, not tables. This matches `heygen video get`.
119+
return ctx.formatter.Data(result, spec.DataField, nil)
108120
}
109121
}
110122

cmd/heygen/builder_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,15 @@ func TestGenBuilder_VideoCreate_Wait_Failure(t *testing.T) {
348348
if !strings.Contains(res.Stderr, "operation reached terminal failure state: failed") {
349349
t.Fatalf("stderr = %s, want failure message", res.Stderr)
350350
}
351+
// Failure response should be output to stdout so users can see error details
352+
var parsed map[string]any
353+
if err := json.Unmarshal([]byte(res.Stdout), &parsed); err != nil {
354+
t.Fatalf("stdout should contain failure response: %v\nstdout: %s", err, res.Stdout)
355+
}
356+
data := parsed["data"].(map[string]any)
357+
if data["status"] != "failed" {
358+
t.Fatalf("status = %v, want failed", data["status"])
359+
}
351360
}
352361

353362
func TestGenBuilder_VideoCreate_Wait_Timeout(t *testing.T) {

cmd/heygen/root.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ func registerGroups(root *cobra.Command, ctx *cmdContext, groups map[string][]*c
9999
}
100100
}
101101

102-
103102
func registerSpecCommand(groupCmd *cobra.Command, spec *command.Spec, ctx *cmdContext) {
104103
path := commandPathParts(spec)
105104
if len(path) == 0 {

internal/client/executor.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ func (e *ErrPaginationTruncated) Error() string {
4141
return fmt.Sprintf("pagination stopped at %d items (hard limit); results may be incomplete", e.Count)
4242
}
4343

44+
// ErrPollFailed is returned when polling reaches a terminal failure state.
45+
// It carries the full status response so callers can output it (the response
46+
// often contains error details the user needs to diagnose the failure).
47+
type ErrPollFailed struct {
48+
Data json.RawMessage
49+
Status string
50+
}
51+
52+
func (e *ErrPollFailed) Error() string {
53+
return fmt.Sprintf("operation reached terminal failure state: %s", e.Status)
54+
}
55+
4456
// ExecuteAndPoll executes a create request, then polls a status endpoint until
4557
// the resource reaches a terminal state or the context is canceled.
4658
func (c *Client) ExecuteAndPoll(
@@ -108,7 +120,7 @@ func (c *Client) ExecuteAndPoll(
108120
return statusResp, nil
109121
}
110122
if slices.Contains(spec.PollConfig.TerminalFail, status) {
111-
return nil, clierrors.New(fmt.Sprintf("operation reached terminal failure state: %s", status))
123+
return nil, &ErrPollFailed{Data: statusResp, Status: status}
112124
}
113125
if opts.OnStatus != nil {
114126
opts.OnStatus(status, time.Since(start))

internal/client/executor_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -817,12 +817,16 @@ func TestExecuteAndPoll_FailureState(t *testing.T) {
817817
t.Fatal("expected error, got nil")
818818
}
819819

820-
var cliErr *clierrors.CLIError
821-
if !errors.As(err, &cliErr) {
822-
t.Fatalf("expected *CLIError, got %T", err)
820+
var failErr *ErrPollFailed
821+
if !errors.As(err, &failErr) {
822+
t.Fatalf("expected *ErrPollFailed, got %T: %v", err, err)
823+
}
824+
if failErr.Status != "failed" {
825+
t.Fatalf("status = %q, want %q", failErr.Status, "failed")
823826
}
824-
if cliErr.Message != "operation reached terminal failure state: failed" {
825-
t.Fatalf("message = %q", cliErr.Message)
827+
// Verify the full response is preserved
828+
if !strings.Contains(string(failErr.Data), `"status":"failed"`) {
829+
t.Fatalf("data = %s, want failure response", failErr.Data)
826830
}
827831
}
828832

0 commit comments

Comments
 (0)