fix(json): standardize JSON output for errors and empty arrays (bd-au0.7)
- Add FatalErrorRespectJSON helper for JSON-aware error output - Fix bd comments list returning null instead of [] for empty arrays - Remove redundant local --json flags from show/update/close commands that were shadowing the global persistent --json flag - Update show command error handlers to use FatalErrorRespectJSON 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1046
.beads/issues.jsonl
1046
.beads/issues.jsonl
File diff suppressed because one or more lines are too long
@@ -35,7 +35,7 @@ Examples:
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
issueID := args[0]
|
issueID := args[0]
|
||||||
|
|
||||||
var comments []*types.Comment
|
comments := make([]*types.Comment, 0)
|
||||||
usedDaemon := false
|
usedDaemon := false
|
||||||
if daemonClient != nil {
|
if daemonClient != nil {
|
||||||
resp, err := daemonClient.ListComments(&rpc.CommentListArgs{ID: issueID})
|
resp, err := daemonClient.ListComments(&rpc.CommentListArgs{ID: issueID})
|
||||||
@@ -79,6 +79,11 @@ Examples:
|
|||||||
comments = result
|
comments = result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize nil to empty slice for consistent JSON output
|
||||||
|
if comments == nil {
|
||||||
|
comments = make([]*types.Comment, 0)
|
||||||
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
data, err := json.MarshalIndent(comments, "", " ")
|
data, err := json.MarshalIndent(comments, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@@ -23,6 +24,28 @@ func FatalError(format string, args ...interface{}) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FatalErrorRespectJSON writes an error message and exits with code 1.
|
||||||
|
// If --json flag is set, outputs structured JSON to stdout.
|
||||||
|
// Otherwise, outputs plain text to stderr.
|
||||||
|
//
|
||||||
|
// Use this for errors in commands that support --json output.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// if err := store.GetIssue(ctx, id); err != nil {
|
||||||
|
// FatalErrorRespectJSON("%v", err)
|
||||||
|
// }
|
||||||
|
func FatalErrorRespectJSON(format string, args ...interface{}) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
if jsonOutput {
|
||||||
|
data, _ := json.MarshalIndent(map[string]string{"error": msg}, "", " ")
|
||||||
|
fmt.Println(string(data))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %s\n", msg)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// FatalErrorWithHint writes an error message with a hint to stderr and exits.
|
// FatalErrorWithHint writes an error message with a hint to stderr and exits.
|
||||||
// Use this when you can provide an actionable suggestion to fix the error.
|
// Use this when you can provide an actionable suggestion to fix the error.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ var showCmd = &cobra.Command{
|
|||||||
Short: "Show issue details",
|
Short: "Show issue details",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
|
||||||
showThread, _ := cmd.Flags().GetBool("thread")
|
showThread, _ := cmd.Flags().GetBool("thread")
|
||||||
ctx := rootCtx
|
ctx := rootCtx
|
||||||
|
|
||||||
@@ -34,8 +33,7 @@ var showCmd = &cobra.Command{
|
|||||||
// Skip check when using daemon (daemon auto-imports on staleness)
|
// Skip check when using daemon (daemon auto-imports on staleness)
|
||||||
if daemonClient == nil {
|
if daemonClient == nil {
|
||||||
if err := ensureDatabaseFresh(ctx); err != nil {
|
if err := ensureDatabaseFresh(ctx); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
FatalErrorRespectJSON("%v", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,13 +45,11 @@ var showCmd = &cobra.Command{
|
|||||||
resolveArgs := &rpc.ResolveIDArgs{ID: id}
|
resolveArgs := &rpc.ResolveIDArgs{ID: id}
|
||||||
resp, err := daemonClient.ResolveID(resolveArgs)
|
resp, err := daemonClient.ResolveID(resolveArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error resolving ID %s: %v\n", id, err)
|
FatalErrorRespectJSON("resolving ID %s: %v", id, err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
var resolvedID string
|
var resolvedID string
|
||||||
if err := json.Unmarshal(resp.Data, &resolvedID); err != nil {
|
if err := json.Unmarshal(resp.Data, &resolvedID); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err)
|
FatalErrorRespectJSON("unmarshaling resolved ID: %v", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
resolvedIDs = append(resolvedIDs, resolvedID)
|
resolvedIDs = append(resolvedIDs, resolvedID)
|
||||||
}
|
}
|
||||||
@@ -62,8 +58,7 @@ var showCmd = &cobra.Command{
|
|||||||
var err error
|
var err error
|
||||||
resolvedIDs, err = utils.ResolvePartialIDs(ctx, store, args)
|
resolvedIDs, err = utils.ResolvePartialIDs(ctx, store, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
FatalErrorRespectJSON("%v", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,7 +461,6 @@ var updateCmd = &cobra.Command{
|
|||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
CheckReadonly("update")
|
CheckReadonly("update")
|
||||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
|
||||||
updates := make(map[string]interface{})
|
updates := make(map[string]interface{})
|
||||||
|
|
||||||
if cmd.Flags().Changed("status") {
|
if cmd.Flags().Changed("status") {
|
||||||
@@ -975,7 +969,6 @@ var closeCmd = &cobra.Command{
|
|||||||
if reason == "" {
|
if reason == "" {
|
||||||
reason = "Closed"
|
reason = "Closed"
|
||||||
}
|
}
|
||||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
|
||||||
force, _ := cmd.Flags().GetBool("force")
|
force, _ := cmd.Flags().GetBool("force")
|
||||||
continueFlag, _ := cmd.Flags().GetBool("continue")
|
continueFlag, _ := cmd.Flags().GetBool("continue")
|
||||||
noAuto, _ := cmd.Flags().GetBool("no-auto")
|
noAuto, _ := cmd.Flags().GetBool("no-auto")
|
||||||
@@ -1383,7 +1376,6 @@ func findReplies(ctx context.Context, issueID string, daemonClient *rpc.Client,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
showCmd.Flags().Bool("json", false, "Output JSON format")
|
|
||||||
showCmd.Flags().Bool("thread", false, "Show full conversation thread (for messages)")
|
showCmd.Flags().Bool("thread", false, "Show full conversation thread (for messages)")
|
||||||
rootCmd.AddCommand(showCmd)
|
rootCmd.AddCommand(showCmd)
|
||||||
|
|
||||||
@@ -1399,8 +1391,6 @@ func init() {
|
|||||||
updateCmd.Flags().StringSlice("add-label", nil, "Add labels (repeatable)")
|
updateCmd.Flags().StringSlice("add-label", nil, "Add labels (repeatable)")
|
||||||
updateCmd.Flags().StringSlice("remove-label", nil, "Remove labels (repeatable)")
|
updateCmd.Flags().StringSlice("remove-label", nil, "Remove labels (repeatable)")
|
||||||
updateCmd.Flags().StringSlice("set-labels", nil, "Set labels, replacing all existing (repeatable)")
|
updateCmd.Flags().StringSlice("set-labels", nil, "Set labels, replacing all existing (repeatable)")
|
||||||
|
|
||||||
updateCmd.Flags().Bool("json", false, "Output JSON format")
|
|
||||||
rootCmd.AddCommand(updateCmd)
|
rootCmd.AddCommand(updateCmd)
|
||||||
|
|
||||||
editCmd.Flags().Bool("title", false, "Edit the title")
|
editCmd.Flags().Bool("title", false, "Edit the title")
|
||||||
@@ -1411,7 +1401,6 @@ func init() {
|
|||||||
rootCmd.AddCommand(editCmd)
|
rootCmd.AddCommand(editCmd)
|
||||||
|
|
||||||
closeCmd.Flags().StringP("reason", "r", "", "Reason for closing")
|
closeCmd.Flags().StringP("reason", "r", "", "Reason for closing")
|
||||||
closeCmd.Flags().Bool("json", false, "Output JSON format")
|
|
||||||
closeCmd.Flags().BoolP("force", "f", false, "Force close pinned issues")
|
closeCmd.Flags().BoolP("force", "f", false, "Force close pinned issues")
|
||||||
closeCmd.Flags().Bool("continue", false, "Auto-advance to next step in molecule")
|
closeCmd.Flags().Bool("continue", false, "Auto-advance to next step in molecule")
|
||||||
closeCmd.Flags().Bool("no-auto", false, "With --continue, show next step but don't claim it")
|
closeCmd.Flags().Bool("no-auto", false, "With --continue, show next step but don't claim it")
|
||||||
|
|||||||
Reference in New Issue
Block a user