fix: Capture stderr instead of suppressing in command execution

Several files were setting cmd.Stderr = nil, which hides potentially
critical error messages:

- prime.go: bd prime, gt mail check, and bd show commands now log
  stderr on failure for debugging
- orphans.go: git fsck now includes stderr in error messages
- patrol_helpers.go: bd list/show/catalog commands now log stderr

Daemon launch cases (up.go, daemon.go, daemon_check.go) correctly
use nil for I/O detachment but now have clarifying comments.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-30 00:49:18 -08:00
parent 4e67f7e637
commit 212d818305
5 changed files with 60 additions and 24 deletions

View File

@@ -124,14 +124,19 @@ func findOrphanCommits(repoPath string) ([]OrphanCommit, error) {
fsckCmd := exec.Command("git", "fsck", "--unreachable", "--no-reflogs")
fsckCmd.Dir = repoPath
var fsckOut bytes.Buffer
var fsckOut, fsckErr bytes.Buffer
fsckCmd.Stdout = &fsckOut
fsckCmd.Stderr = nil // Ignore warnings
fsckCmd.Stderr = &fsckErr
if err := fsckCmd.Run(); err != nil {
// git fsck returns non-zero if there are issues, but we still get output
// Only fail if we got no output at all
if fsckOut.Len() == 0 {
// Include stderr in error message for debugging
errMsg := strings.TrimSpace(fsckErr.String())
if errMsg != "" {
return nil, fmt.Errorf("git fsck failed: %w (%s)", err, errMsg)
}
return nil, fmt.Errorf("git fsck failed: %w", err)
}
}

View File

@@ -3,6 +3,7 @@ package cmd
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
@@ -28,11 +29,15 @@ func findActivePatrol(cfg PatrolConfig) (patrolID, patrolLine string, found bool
if cfg.CheckInProgress {
cmdList := exec.Command("bd", "--no-daemon", "list", "--status=in_progress", "--type=epic")
cmdList.Dir = cfg.BeadsDir
var stdoutList bytes.Buffer
var stdoutList, stderrList bytes.Buffer
cmdList.Stdout = &stdoutList
cmdList.Stderr = nil
cmdList.Stderr = &stderrList
if cmdList.Run() == nil {
if err := cmdList.Run(); err != nil {
if errMsg := strings.TrimSpace(stderrList.String()); errMsg != "" {
fmt.Fprintf(os.Stderr, "bd list: %s\n", errMsg)
}
} else {
lines := strings.Split(stdoutList.String(), "\n")
for _, line := range lines {
if strings.Contains(line, cfg.PatrolMolName) && !strings.Contains(line, "[template]") {
@@ -48,11 +53,15 @@ func findActivePatrol(cfg PatrolConfig) (patrolID, patrolLine string, found bool
// Check for open patrols with open children (active wisp)
cmdOpen := exec.Command("bd", "--no-daemon", "list", "--status=open", "--type=epic")
cmdOpen.Dir = cfg.BeadsDir
var stdoutOpen bytes.Buffer
var stdoutOpen, stderrOpen bytes.Buffer
cmdOpen.Stdout = &stdoutOpen
cmdOpen.Stderr = nil
cmdOpen.Stderr = &stderrOpen
if cmdOpen.Run() == nil {
if err := cmdOpen.Run(); err != nil {
if errMsg := strings.TrimSpace(stderrOpen.String()); errMsg != "" {
fmt.Fprintf(os.Stderr, "bd list: %s\n", errMsg)
}
} else {
lines := strings.Split(stdoutOpen.String(), "\n")
for _, line := range lines {
if strings.Contains(line, cfg.PatrolMolName) && !strings.Contains(line, "[template]") {
@@ -62,10 +71,14 @@ func findActivePatrol(cfg PatrolConfig) (patrolID, patrolLine string, found bool
// Check if this molecule has open children
cmdShow := exec.Command("bd", "--no-daemon", "show", molID)
cmdShow.Dir = cfg.BeadsDir
var stdoutShow bytes.Buffer
var stdoutShow, stderrShow bytes.Buffer
cmdShow.Stdout = &stdoutShow
cmdShow.Stderr = nil
if cmdShow.Run() == nil {
cmdShow.Stderr = &stderrShow
if err := cmdShow.Run(); err != nil {
if errMsg := strings.TrimSpace(stderrShow.String()); errMsg != "" {
fmt.Fprintf(os.Stderr, "bd show: %s\n", errMsg)
}
} else {
showOutput := stdoutShow.String()
// Deacon only checks "- open]", witness/refinery also check "- in_progress]"
hasOpenChildren := strings.Contains(showOutput, "- open]")
@@ -90,12 +103,16 @@ func autoSpawnPatrol(cfg PatrolConfig) (string, error) {
// Find the proto ID for the patrol molecule
cmdCatalog := exec.Command("bd", "--no-daemon", "mol", "catalog")
cmdCatalog.Dir = cfg.BeadsDir
var stdoutCatalog bytes.Buffer
var stdoutCatalog, stderrCatalog bytes.Buffer
cmdCatalog.Stdout = &stdoutCatalog
cmdCatalog.Stderr = nil
cmdCatalog.Stderr = &stderrCatalog
if err := cmdCatalog.Run(); err != nil {
return "", fmt.Errorf("failed to list molecule catalog")
errMsg := strings.TrimSpace(stderrCatalog.String())
if errMsg != "" {
return "", fmt.Errorf("failed to list molecule catalog: %s", errMsg)
}
return "", fmt.Errorf("failed to list molecule catalog: %w", err)
}
// Find patrol molecule in catalog

View File

@@ -429,12 +429,16 @@ func runBdPrime(workDir string) {
cmd := exec.Command("bd", "prime")
cmd.Dir = workDir
var stdout bytes.Buffer
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = nil // Ignore stderr
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
// Silently skip if bd prime fails (beads might not be available)
// Skip if bd prime fails (beads might not be available)
// But log stderr if present for debugging
if errMsg := strings.TrimSpace(stderr.String()); errMsg != "" {
fmt.Fprintf(os.Stderr, "bd prime: %s\n", errMsg)
}
return
}
@@ -521,12 +525,15 @@ func runMailCheckInject(workDir string) {
cmd := exec.Command("gt", "mail", "check", "--inject")
cmd.Dir = workDir
var stdout bytes.Buffer
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = nil // Ignore stderr
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
// Silently skip if mail check fails
// Skip if mail check fails, but log stderr for debugging
if errMsg := strings.TrimSpace(stderr.String()); errMsg != "" {
fmt.Fprintf(os.Stderr, "gt mail check: %s\n", errMsg)
}
return
}
@@ -947,10 +954,16 @@ func checkSlungWork(ctx RoleContext) bool {
// Show bead preview using bd show
fmt.Println("**Bead details:**")
cmd := exec.Command("bd", "show", hookedBead.ID)
var stdout bytes.Buffer
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = nil
if cmd.Run() == nil {
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
if errMsg := strings.TrimSpace(stderr.String()); errMsg != "" {
fmt.Fprintf(os.Stderr, " bd show %s: %s\n", hookedBead.ID, errMsg)
} else {
fmt.Fprintf(os.Stderr, " bd show %s: %v\n", hookedBead.ID, err)
}
} else {
lines := strings.Split(stdout.String(), "\n")
maxLines := 15
if len(lines) > maxLines {

View File

@@ -196,6 +196,7 @@ func ensureDaemon(townRoot string) error {
cmd := exec.Command(gtPath, "daemon", "run")
cmd.Dir = townRoot
// Detach from parent I/O for background daemon (uses its own logging)
cmd.Stdin = nil
cmd.Stdout = nil
cmd.Stderr = nil

View File

@@ -73,7 +73,7 @@ func (c *DaemonCheck) Fix(ctx *CheckContext) error {
return err
}
// Start daemon in background
// Start daemon in background (detach from parent I/O - daemon uses its own logging)
cmd := exec.Command(gtPath, "daemon", "run")
cmd.Dir = ctx.TownRoot
cmd.Stdin = nil