fix(daemon): improve error handling and security (#445)

* fix(beads): cache version check and add timeout to prevent cli lag

* fix(mail_queue): add nil check for queue config

Prevents potential nil pointer panic when queue config exists
in map but has nil value. Added || queueCfg == nil check to
the queue lookup condition in runMailClaim function.

Fixes potential panic that could occur if a queue entry exists
in config but with a nil value.

* fix(migrate_agents_test): fix icon expectations to match actual output

The printMigrationResult function uses icons with two leading spaces
("  ✓", "  ⊘", "  ✗") but the test expected icons without spaces.
This fixes the test expectations to match the actual output format.

* fix(hook): handle error from events.LogFeed

Previously the error from LogFeed was silently ignored with _.
Now we log the error to stderr at warning level but don't fail
the operation since the primary hook action succeeded.

* fix(tmux): security and error handling improvements

- Fix unchecked regexp error in IsClaudeRunning (CVE-like)
- Add input sanitization to SetPaneDiedHook to prevent shell injection
- Add session name validation to SetDynamicStatus
- Sanitize mail from/subject in SendNotificationBanner
- Return error on parse failure in GetEnvironment
- Track skipped lines in ListSessionIDs for debuggability

See: tmux.fix for full analysis

* fix(daemon): improve error handling and security

- Capture stderr in syncWorkspace for better debuggability
- Fail fast on git fetch failures to prevent stale code
- Add logging to previously silent bd list errors
- Change notification state file permissions to 0600
- Improve error messages with actual stderr content

This prevents agents from starting with stale code and provides
better visibility into daemon operations.
This commit is contained in:
sigfawn
2026-01-14 01:13:54 -05:00
committed by GitHub
parent a1195cb104
commit 3cf77b2e8b
4 changed files with 67 additions and 13 deletions

View File

@@ -1,6 +1,7 @@
package daemon
import (
"bytes"
"encoding/json"
"fmt"
"os"
@@ -42,7 +43,7 @@ func (d *Daemon) ProcessLifecycleRequests() {
output, err := cmd.Output()
if err != nil {
// gt mail might not be available or inbox empty
d.logger.Printf("Warning: failed to fetch deacon inbox: %v", err)
return
}
@@ -563,26 +564,52 @@ func (d *Daemon) syncWorkspace(workDir string) {
}
}
// Capture stderr for debuggability
var stderr bytes.Buffer
// Fetch latest from origin
fetchCmd := exec.Command("git", "fetch", "origin")
fetchCmd.Dir = workDir
fetchCmd.Stderr = &stderr
if err := fetchCmd.Run(); err != nil {
d.logger.Printf("Warning: git fetch failed in %s: %v", workDir, err)
errMsg := strings.TrimSpace(stderr.String())
if errMsg == "" {
errMsg = err.Error()
}
d.logger.Printf("Error: git fetch failed in %s: %s", workDir, errMsg)
return // Fail fast - don't start agent with stale code
}
// Reset stderr buffer
stderr.Reset()
// Pull with rebase to incorporate changes
pullCmd := exec.Command("git", "pull", "--rebase", "origin", defaultBranch)
pullCmd.Dir = workDir
pullCmd.Stderr = &stderr
if err := pullCmd.Run(); err != nil {
d.logger.Printf("Warning: git pull failed in %s: %v", workDir, err)
errMsg := strings.TrimSpace(stderr.String())
if errMsg == "" {
errMsg = err.Error()
}
d.logger.Printf("Warning: git pull failed in %s: %s (agent may have conflicts)", workDir, errMsg)
// Don't fail - agent can handle conflicts
}
// Reset stderr buffer
stderr.Reset()
// Sync beads
bdCmd := exec.Command("bd", "sync")
bdCmd.Dir = workDir
bdCmd.Stderr = &stderr
if err := bdCmd.Run(); err != nil {
d.logger.Printf("Warning: bd sync failed in %s: %v", workDir, err)
errMsg := strings.TrimSpace(stderr.String())
if errMsg == "" {
errMsg = err.Error()
}
d.logger.Printf("Warning: bd sync failed in %s: %s", workDir, errMsg)
// Don't fail - sync issues may be recoverable
}
}
@@ -771,7 +798,8 @@ func (d *Daemon) checkRigGUPPViolations(rigName string) {
output, err := cmd.Output()
if err != nil {
return // Silently fail - bd might not be available
d.logger.Printf("Warning: bd list failed for GUPP check: %v", err)
return
}
var agents []struct {
@@ -868,6 +896,7 @@ func (d *Daemon) checkRigOrphanedWork(rigName string) {
output, err := cmd.Output()
if err != nil {
d.logger.Printf("Warning: bd list failed for orphaned work check: %v", err)
return
}

View File

@@ -115,7 +115,7 @@ func (m *NotificationManager) RecordSend(session, slot, message string) error {
return err
}
return os.WriteFile(m.slotPath(session, slot), data, 0644)
return os.WriteFile(m.slotPath(session, slot), data, 0600)
}
// MarkConsumed marks a slot's notification as consumed (agent responded).
@@ -137,7 +137,7 @@ func (m *NotificationManager) MarkConsumed(session, slot string) error {
return err
}
return os.WriteFile(m.slotPath(session, slot), data, 0644)
return os.WriteFile(m.slotPath(session, slot), data, 0600)
}
// MarkSessionActive marks all slots for a session as consumed.