diff --git a/internal/beads/beads.go b/internal/beads/beads.go index 9b67b638..9dbb396d 100644 --- a/internal/beads/beads.go +++ b/internal/beads/beads.go @@ -151,6 +151,13 @@ func (b *Beads) run(args ...string) ([]byte, error) { return nil, b.wrapError(err, stderr.String(), args) } + // Handle bd --no-daemon exit code 0 bug: when issue not found, + // --no-daemon exits 0 but writes error to stderr with empty stdout. + // Detect this case and treat as error to avoid JSON parse failures. + if stdout.Len() == 0 && stderr.Len() > 0 { + return nil, b.wrapError(fmt.Errorf("command produced no output"), stderr.String(), args) + } + return stdout.Bytes(), nil } @@ -174,7 +181,9 @@ func (b *Beads) wrapError(err error, stderr string, args []string) error { } // ErrNotFound is widely used for issue lookups - acceptable exception - if strings.Contains(stderr, "not found") || strings.Contains(stderr, "Issue not found") { + // Match various "not found" error patterns from bd + if strings.Contains(stderr, "not found") || strings.Contains(stderr, "Issue not found") || + strings.Contains(stderr, "no issue found") { return ErrNotFound } diff --git a/internal/cmd/convoy.go b/internal/cmd/convoy.go index 3ea0babb..a6a19111 100644 --- a/internal/cmd/convoy.go +++ b/internal/cmd/convoy.go @@ -1186,6 +1186,10 @@ func getIssueDetails(issueID string) *issueDetails { if err := showCmd.Run(); err != nil { return nil } + // Handle bd --no-daemon exit 0 bug: empty stdout means not found + if stdout.Len() == 0 { + return nil + } var issues []struct { ID string `json:"id"` diff --git a/internal/cmd/prime_molecule.go b/internal/cmd/prime_molecule.go index 661143bb..d8cdf62a 100644 --- a/internal/cmd/prime_molecule.go +++ b/internal/cmd/prime_molecule.go @@ -44,6 +44,12 @@ func showMoleculeExecutionPrompt(workDir, moleculeID string) { fmt.Printf(" Check status with: bd mol current %s\n", moleculeID) return } + // Handle bd --no-daemon exit 0 bug: empty stdout means not found + if stdout.Len() == 0 { + fmt.Println(style.Bold.Render("→ PROPULSION PRINCIPLE: Work is on your hook. RUN IT.")) + fmt.Println(" Begin working on this molecule immediately.") + return + } // Parse JSON output - it's an array with one element var outputs []MoleculeCurrentOutput diff --git a/internal/cmd/sling.go b/internal/cmd/sling.go index 523068b4..f649500d 100644 --- a/internal/cmd/sling.go +++ b/internal/cmd/sling.go @@ -523,6 +523,10 @@ func storeArgsInBead(beadID, args string) error { if err != nil { return fmt.Errorf("fetching bead: %w", err) } + // Handle bd --no-daemon exit 0 bug: empty stdout means not found + if len(out) == 0 { + return fmt.Errorf("bead not found") + } // Parse the bead var issues []beads.Issue @@ -738,9 +742,15 @@ func verifyBeadExists(beadID string) error { if townRoot, err := workspace.FindFromCwd(); err == nil { cmd.Dir = townRoot } - if err := cmd.Run(); err != nil { + // Use Output() instead of Run() to detect bd --no-daemon exit 0 bug: + // when issue not found, --no-daemon exits 0 but produces empty stdout. + out, err := cmd.Output() + if err != nil { return fmt.Errorf("bead '%s' not found (bd show failed)", beadID) } + if len(out) == 0 { + return fmt.Errorf("bead '%s' not found", beadID) + } return nil } @@ -764,6 +774,11 @@ func getBeadInfo(beadID string) (*beadInfo, error) { if err != nil { return nil, fmt.Errorf("bead '%s' not found", beadID) } + // Handle bd --no-daemon exit 0 bug: when issue not found, + // --no-daemon exits 0 but produces empty stdout (error goes to stderr). + if len(out) == 0 { + return nil, fmt.Errorf("bead '%s' not found", beadID) + } // bd show --json returns an array (issue + dependents), take first element var infos []beadInfo if err := json.Unmarshal(out, &infos); err != nil { @@ -829,14 +844,16 @@ func resolveSelfTarget() (agentID string, pane string, hookRoot string, err erro // Uses --no-daemon with --allow-stale for consistency with verifyBeadExists. func verifyFormulaExists(formulaName string) error { // Try bd formula show (handles all formula file formats) + // Use Output() instead of Run() to detect bd --no-daemon exit 0 bug: + // when formula not found, --no-daemon may exit 0 but produce empty stdout. cmd := exec.Command("bd", "--no-daemon", "formula", "show", formulaName, "--allow-stale") - if err := cmd.Run(); err == nil { + if out, err := cmd.Output(); err == nil && len(out) > 0 { return nil } // Try with mol- prefix cmd = exec.Command("bd", "--no-daemon", "formula", "show", "mol-"+formulaName, "--allow-stale") - if err := cmd.Run(); err == nil { + if out, err := cmd.Output(); err == nil && len(out) > 0 { return nil }