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:
@@ -124,14 +124,19 @@ func findOrphanCommits(repoPath string) ([]OrphanCommit, error) {
|
|||||||
fsckCmd := exec.Command("git", "fsck", "--unreachable", "--no-reflogs")
|
fsckCmd := exec.Command("git", "fsck", "--unreachable", "--no-reflogs")
|
||||||
fsckCmd.Dir = repoPath
|
fsckCmd.Dir = repoPath
|
||||||
|
|
||||||
var fsckOut bytes.Buffer
|
var fsckOut, fsckErr bytes.Buffer
|
||||||
fsckCmd.Stdout = &fsckOut
|
fsckCmd.Stdout = &fsckOut
|
||||||
fsckCmd.Stderr = nil // Ignore warnings
|
fsckCmd.Stderr = &fsckErr
|
||||||
|
|
||||||
if err := fsckCmd.Run(); err != nil {
|
if err := fsckCmd.Run(); err != nil {
|
||||||
// git fsck returns non-zero if there are issues, but we still get output
|
// git fsck returns non-zero if there are issues, but we still get output
|
||||||
// Only fail if we got no output at all
|
// Only fail if we got no output at all
|
||||||
if fsckOut.Len() == 0 {
|
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)
|
return nil, fmt.Errorf("git fsck failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -28,11 +29,15 @@ func findActivePatrol(cfg PatrolConfig) (patrolID, patrolLine string, found bool
|
|||||||
if cfg.CheckInProgress {
|
if cfg.CheckInProgress {
|
||||||
cmdList := exec.Command("bd", "--no-daemon", "list", "--status=in_progress", "--type=epic")
|
cmdList := exec.Command("bd", "--no-daemon", "list", "--status=in_progress", "--type=epic")
|
||||||
cmdList.Dir = cfg.BeadsDir
|
cmdList.Dir = cfg.BeadsDir
|
||||||
var stdoutList bytes.Buffer
|
var stdoutList, stderrList bytes.Buffer
|
||||||
cmdList.Stdout = &stdoutList
|
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")
|
lines := strings.Split(stdoutList.String(), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if strings.Contains(line, cfg.PatrolMolName) && !strings.Contains(line, "[template]") {
|
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)
|
// Check for open patrols with open children (active wisp)
|
||||||
cmdOpen := exec.Command("bd", "--no-daemon", "list", "--status=open", "--type=epic")
|
cmdOpen := exec.Command("bd", "--no-daemon", "list", "--status=open", "--type=epic")
|
||||||
cmdOpen.Dir = cfg.BeadsDir
|
cmdOpen.Dir = cfg.BeadsDir
|
||||||
var stdoutOpen bytes.Buffer
|
var stdoutOpen, stderrOpen bytes.Buffer
|
||||||
cmdOpen.Stdout = &stdoutOpen
|
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")
|
lines := strings.Split(stdoutOpen.String(), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if strings.Contains(line, cfg.PatrolMolName) && !strings.Contains(line, "[template]") {
|
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
|
// Check if this molecule has open children
|
||||||
cmdShow := exec.Command("bd", "--no-daemon", "show", molID)
|
cmdShow := exec.Command("bd", "--no-daemon", "show", molID)
|
||||||
cmdShow.Dir = cfg.BeadsDir
|
cmdShow.Dir = cfg.BeadsDir
|
||||||
var stdoutShow bytes.Buffer
|
var stdoutShow, stderrShow bytes.Buffer
|
||||||
cmdShow.Stdout = &stdoutShow
|
cmdShow.Stdout = &stdoutShow
|
||||||
cmdShow.Stderr = nil
|
cmdShow.Stderr = &stderrShow
|
||||||
if cmdShow.Run() == nil {
|
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()
|
showOutput := stdoutShow.String()
|
||||||
// Deacon only checks "- open]", witness/refinery also check "- in_progress]"
|
// Deacon only checks "- open]", witness/refinery also check "- in_progress]"
|
||||||
hasOpenChildren := strings.Contains(showOutput, "- open]")
|
hasOpenChildren := strings.Contains(showOutput, "- open]")
|
||||||
@@ -90,12 +103,16 @@ func autoSpawnPatrol(cfg PatrolConfig) (string, error) {
|
|||||||
// Find the proto ID for the patrol molecule
|
// Find the proto ID for the patrol molecule
|
||||||
cmdCatalog := exec.Command("bd", "--no-daemon", "mol", "catalog")
|
cmdCatalog := exec.Command("bd", "--no-daemon", "mol", "catalog")
|
||||||
cmdCatalog.Dir = cfg.BeadsDir
|
cmdCatalog.Dir = cfg.BeadsDir
|
||||||
var stdoutCatalog bytes.Buffer
|
var stdoutCatalog, stderrCatalog bytes.Buffer
|
||||||
cmdCatalog.Stdout = &stdoutCatalog
|
cmdCatalog.Stdout = &stdoutCatalog
|
||||||
cmdCatalog.Stderr = nil
|
cmdCatalog.Stderr = &stderrCatalog
|
||||||
|
|
||||||
if err := cmdCatalog.Run(); err != nil {
|
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
|
// Find patrol molecule in catalog
|
||||||
|
|||||||
@@ -429,12 +429,16 @@ func runBdPrime(workDir string) {
|
|||||||
cmd := exec.Command("bd", "prime")
|
cmd := exec.Command("bd", "prime")
|
||||||
cmd.Dir = workDir
|
cmd.Dir = workDir
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
cmd.Stdout = &stdout
|
cmd.Stdout = &stdout
|
||||||
cmd.Stderr = nil // Ignore stderr
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,12 +525,15 @@ func runMailCheckInject(workDir string) {
|
|||||||
cmd := exec.Command("gt", "mail", "check", "--inject")
|
cmd := exec.Command("gt", "mail", "check", "--inject")
|
||||||
cmd.Dir = workDir
|
cmd.Dir = workDir
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
cmd.Stdout = &stdout
|
cmd.Stdout = &stdout
|
||||||
cmd.Stderr = nil // Ignore stderr
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -947,10 +954,16 @@ func checkSlungWork(ctx RoleContext) bool {
|
|||||||
// Show bead preview using bd show
|
// Show bead preview using bd show
|
||||||
fmt.Println("**Bead details:**")
|
fmt.Println("**Bead details:**")
|
||||||
cmd := exec.Command("bd", "show", hookedBead.ID)
|
cmd := exec.Command("bd", "show", hookedBead.ID)
|
||||||
var stdout bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
cmd.Stdout = &stdout
|
cmd.Stdout = &stdout
|
||||||
cmd.Stderr = nil
|
cmd.Stderr = &stderr
|
||||||
if cmd.Run() == nil {
|
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")
|
lines := strings.Split(stdout.String(), "\n")
|
||||||
maxLines := 15
|
maxLines := 15
|
||||||
if len(lines) > maxLines {
|
if len(lines) > maxLines {
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ func ensureDaemon(townRoot string) error {
|
|||||||
|
|
||||||
cmd := exec.Command(gtPath, "daemon", "run")
|
cmd := exec.Command(gtPath, "daemon", "run")
|
||||||
cmd.Dir = townRoot
|
cmd.Dir = townRoot
|
||||||
|
// Detach from parent I/O for background daemon (uses its own logging)
|
||||||
cmd.Stdin = nil
|
cmd.Stdin = nil
|
||||||
cmd.Stdout = nil
|
cmd.Stdout = nil
|
||||||
cmd.Stderr = nil
|
cmd.Stderr = nil
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (c *DaemonCheck) Fix(ctx *CheckContext) error {
|
|||||||
return err
|
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 := exec.Command(gtPath, "daemon", "run")
|
||||||
cmd.Dir = ctx.TownRoot
|
cmd.Dir = ctx.TownRoot
|
||||||
cmd.Stdin = nil
|
cmd.Stdin = nil
|
||||||
|
|||||||
Reference in New Issue
Block a user