Document intentional error suppressions with comments (gt-zn9m)
All 156 instances of _ = error suppression in non-test code now have explanatory comments documenting why the error is intentionally ignored. Categories of intentional suppressions: - non-fatal: session works without these - tmux environment setup - non-fatal: theming failure does not affect operation - visual styling - best-effort cleanup - defer cleanup on failure paths - best-effort notification - mail/notifications that should not block - best-effort interrupt - graceful shutdown attempts - crypto/rand.Read only fails on broken system - random ID generation - output errors non-actionable - fmt.Fprint to io.Writer This addresses the silent failure and debugging concerns raised in the issue by making the intentionality explicit in the code. Generated with Claude Code https://claude.com/claude-code Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -247,7 +247,7 @@ func (b *Beads) instantiateFromChildren(mol *Issue, parent *Issue, templates []*
|
||||
|
||||
child, err := b.Create(childOpts)
|
||||
if err != nil {
|
||||
// Attempt to clean up created issues on failure
|
||||
// Attempt to clean up created issues on failure (best-effort cleanup)
|
||||
for _, created := range createdIssues {
|
||||
_ = b.Close(created.ID)
|
||||
}
|
||||
@@ -340,7 +340,7 @@ func (b *Beads) instantiateFromMarkdown(mol *Issue, parent *Issue, opts Instanti
|
||||
|
||||
child, err := b.Create(childOpts)
|
||||
if err != nil {
|
||||
// Attempt to clean up created issues on failure
|
||||
// Attempt to clean up created issues on failure (best-effort cleanup)
|
||||
for _, created := range createdIssues {
|
||||
_ = b.Close(created.ID)
|
||||
}
|
||||
|
||||
@@ -90,20 +90,20 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(sessionID, "GT_RIG", r.Name)
|
||||
_ = t.SetEnvironment(sessionID, "GT_CREW", name)
|
||||
|
||||
// Set CLAUDE_CONFIG_DIR for account selection
|
||||
// Set CLAUDE_CONFIG_DIR for account selection (non-fatal)
|
||||
if claudeConfigDir != "" {
|
||||
_ = t.SetEnvironment(sessionID, "CLAUDE_CONFIG_DIR", claudeConfigDir)
|
||||
}
|
||||
|
||||
// Apply rig-based theming (uses config if set, falls back to hash)
|
||||
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
||||
theme := getThemeForRig(r.Name)
|
||||
_ = t.ConfigureGasTownSession(sessionID, theme, r.Name, name, "crew")
|
||||
|
||||
// Set up C-b n/p keybindings for crew session cycling
|
||||
// Set up C-b n/p keybindings for crew session cycling (non-fatal)
|
||||
_ = t.SetCrewCycleBindings(sessionID)
|
||||
|
||||
// Wait for shell to be ready after session creation
|
||||
|
||||
@@ -135,7 +135,7 @@ func runCrewRefresh(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(sessionID, "GT_RIG", r.Name)
|
||||
_ = t.SetEnvironment(sessionID, "GT_CREW", name)
|
||||
|
||||
@@ -224,7 +224,7 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
|
||||
t.SetEnvironment(sessionID, "GT_RIG", r.Name)
|
||||
t.SetEnvironment(sessionID, "GT_CREW", name)
|
||||
|
||||
// Apply rig-based theming (uses config if set, falls back to hash)
|
||||
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
||||
theme := getThemeForRig(r.Name)
|
||||
_ = t.ConfigureGasTownSession(sessionID, theme, r.Name, name, "crew")
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ func runCrewStatus(cmd *cobra.Command, args []string) error {
|
||||
untracked = gitStatus.Untracked
|
||||
}
|
||||
|
||||
// Mail status
|
||||
// Mail status (non-fatal: display defaults to 0 if count fails)
|
||||
mailDir := filepath.Join(w.ClonePath, "mail")
|
||||
mailTotal, mailUnread := 0, 0
|
||||
if _, err := os.Stat(mailDir); err == nil {
|
||||
|
||||
@@ -175,11 +175,11 @@ func startDeaconSession(t *tmux.Tmux) error {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(DeaconSessionName, "GT_ROLE", "deacon")
|
||||
_ = t.SetEnvironment(DeaconSessionName, "BD_ACTOR", "deacon")
|
||||
|
||||
// Apply Deacon theme
|
||||
// Apply Deacon theme (non-fatal: theming failure doesn't affect operation)
|
||||
theme := tmux.DeaconTheme()
|
||||
_ = t.ConfigureGasTownSession(DeaconSessionName, theme, "", "Deacon", "health-check")
|
||||
|
||||
@@ -208,7 +208,7 @@ func runDeaconStop(cmd *cobra.Command, args []string) error {
|
||||
|
||||
fmt.Println("Stopping Deacon session...")
|
||||
|
||||
// Try graceful shutdown first
|
||||
// Try graceful shutdown first (best-effort interrupt)
|
||||
_ = t.SendKeysRaw(DeaconSessionName, "C-c")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ func stopSession(t *tmux.Tmux, sessionName string) error {
|
||||
return nil // Already stopped
|
||||
}
|
||||
|
||||
// Try graceful shutdown first (Ctrl-C)
|
||||
// Try graceful shutdown first (Ctrl-C, best-effort interrupt)
|
||||
if !downForce {
|
||||
_ = t.SendKeysRaw(sessionName, "C-c")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
@@ -62,7 +62,7 @@ func runInit(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("creating %s: %w", dir, err)
|
||||
}
|
||||
|
||||
// Create .gitkeep to ensure directory is tracked if needed
|
||||
// Create .gitkeep to ensure directory is tracked if needed (non-fatal)
|
||||
gitkeep := filepath.Join(dirPath, ".gitkeep")
|
||||
if _, err := os.Stat(gitkeep); os.IsNotExist(err) {
|
||||
_ = os.WriteFile(gitkeep, []byte(""), 0644)
|
||||
|
||||
@@ -241,7 +241,7 @@ func init() {
|
||||
mailSendCmd.Flags().BoolVar(&mailWisp, "wisp", true, "Send as wisp (ephemeral, default)")
|
||||
mailSendCmd.Flags().BoolVar(&mailPermanent, "permanent", false, "Send as permanent (not ephemeral, synced to remote)")
|
||||
mailSendCmd.Flags().BoolVar(&mailSendSelf, "self", false, "Send to self (auto-detect from cwd)")
|
||||
_ = mailSendCmd.MarkFlagRequired("subject")
|
||||
_ = mailSendCmd.MarkFlagRequired("subject") // cobra flags: error only at runtime if missing
|
||||
|
||||
// Inbox flags
|
||||
mailInboxCmd.Flags().BoolVar(&mailInboxJSON, "json", false, "Output as JSON")
|
||||
@@ -949,6 +949,6 @@ func runMailReply(cmd *cobra.Command, args []string) error {
|
||||
// generateThreadID creates a random thread ID for new message threads.
|
||||
func generateThreadID() string {
|
||||
b := make([]byte, 6)
|
||||
_, _ = rand.Read(b)
|
||||
_, _ = rand.Read(b) // crypto/rand.Read only fails on broken system
|
||||
return "thread-" + hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
@@ -118,11 +118,11 @@ func startMayorSession(t *tmux.Tmux) error {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(MayorSessionName, "GT_ROLE", "mayor")
|
||||
_ = t.SetEnvironment(MayorSessionName, "BD_ACTOR", "mayor")
|
||||
|
||||
// Apply Mayor theme
|
||||
// Apply Mayor theme (non-fatal: theming failure doesn't affect operation)
|
||||
theme := tmux.MayorTheme()
|
||||
_ = t.ConfigureGasTownSession(MayorSessionName, theme, "", "Mayor", "coordinator")
|
||||
|
||||
@@ -151,7 +151,7 @@ func runMayorStop(cmd *cobra.Command, args []string) error {
|
||||
|
||||
fmt.Println("Stopping Mayor session...")
|
||||
|
||||
// Try graceful shutdown first
|
||||
// Try graceful shutdown first (best-effort interrupt)
|
||||
_ = t.SendKeysRaw(MayorSessionName, "C-c")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
@@ -230,7 +230,7 @@ func runMayorRestart(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
if running {
|
||||
// Stop the current session
|
||||
// Stop the current session (best-effort interrupt before kill)
|
||||
fmt.Println("Stopping Mayor session...")
|
||||
_ = t.SendKeysRaw(MayorSessionName, "C-c")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
@@ -406,7 +406,7 @@ func init() {
|
||||
func loadMoleculeCatalog(workDir string) (*beads.MoleculeCatalog, error) {
|
||||
var townRoot, rigPath, projectPath string
|
||||
|
||||
// Try to find town root
|
||||
// Try to find town root (non-fatal: falls back to local formulas)
|
||||
townRoot, _ = workspace.FindFromCwd()
|
||||
|
||||
// Try to find rig path
|
||||
|
||||
@@ -177,7 +177,7 @@ bonded_at: %s
|
||||
opts := beads.InstantiateOptions{Context: ctx}
|
||||
steps, err := b.InstantiateMolecule(proto, child, opts)
|
||||
if err != nil {
|
||||
// Clean up the child container on failure
|
||||
// Clean up the child container on failure (best-effort cleanup)
|
||||
_ = b.Close(child.ID)
|
||||
return fmt.Errorf("instantiating bonded molecule: %w", err)
|
||||
}
|
||||
@@ -481,7 +481,7 @@ squashed_at: %s
|
||||
return fmt.Errorf("creating digest: %w", err)
|
||||
}
|
||||
|
||||
// Add the digest label
|
||||
// Add the digest label (non-fatal: digest works without label)
|
||||
_ = b.Update(digestIssue.ID, beads.UpdateOptions{
|
||||
AddLabels: []string{"digest"},
|
||||
})
|
||||
|
||||
@@ -166,7 +166,7 @@ func runMoleculeShow(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// Parse steps
|
||||
steps, parseErr := beads.ParseMoleculeSteps(mol.Description)
|
||||
_ = source // Used below in output
|
||||
_ = source // silence unused warning; used in output formatting below
|
||||
|
||||
// For JSON, include parsed steps
|
||||
if moleculeJSON {
|
||||
|
||||
+1
-1
@@ -269,7 +269,7 @@ func init() {
|
||||
// Reject flags
|
||||
mqRejectCmd.Flags().StringVarP(&mqRejectReason, "reason", "r", "", "Reason for rejection (required)")
|
||||
mqRejectCmd.Flags().BoolVar(&mqRejectNotify, "notify", false, "Send mail notification to worker")
|
||||
_ = mqRejectCmd.MarkFlagRequired("reason")
|
||||
_ = mqRejectCmd.MarkFlagRequired("reason") // cobra flags: error only at runtime if missing
|
||||
|
||||
// Status flags
|
||||
mqStatusCmd.Flags().BoolVar(&mqStatusJSON, "json", false, "Output as JSON")
|
||||
|
||||
@@ -106,7 +106,7 @@ func runMqIntegrationCreate(cmd *cobra.Command, args []string) error {
|
||||
// 3. Push to origin
|
||||
fmt.Printf("Pushing to origin...\n")
|
||||
if err := g.Push("origin", branchName, false); err != nil {
|
||||
// Clean up local branch on push failure
|
||||
// Clean up local branch on push failure (best-effort cleanup)
|
||||
_ = g.DeleteBranch(branchName, true)
|
||||
return fmt.Errorf("pushing to origin: %w", err)
|
||||
}
|
||||
@@ -299,7 +299,7 @@ func runMqIntegrationLand(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Merging %s to main...\n", branchName)
|
||||
mergeMsg := fmt.Sprintf("Merge %s: %s\n\nEpic: %s", branchName, epic.Title, epicID)
|
||||
if err := g.MergeNoFF("origin/"+branchName, mergeMsg); err != nil {
|
||||
// Abort merge on failure
|
||||
// Abort merge on failure (best-effort cleanup)
|
||||
_ = g.AbortMerge()
|
||||
return fmt.Errorf("merge failed: %w", err)
|
||||
}
|
||||
@@ -313,7 +313,7 @@ func runMqIntegrationLand(cmd *cobra.Command, args []string) error {
|
||||
if err := runTestCommand(r.Path, testCmd); err != nil {
|
||||
// Tests failed - reset main
|
||||
fmt.Printf(" %s Tests failed, resetting main...\n", style.Bold.Render("✗"))
|
||||
_ = g.Checkout("main")
|
||||
_ = g.Checkout("main") // best-effort: need to be on main to reset
|
||||
resetErr := resetHard(g, "HEAD~1")
|
||||
if resetErr != nil {
|
||||
return fmt.Errorf("tests failed and could not reset: %w (test error: %v)", resetErr, err)
|
||||
|
||||
@@ -957,7 +957,7 @@ func getGitState(worktreePath string) (*GitState, error) {
|
||||
// origin/main might not exist - try origin/master
|
||||
logCmd = exec.Command("git", "log", "origin/master..HEAD", "--oneline")
|
||||
logCmd.Dir = worktreePath
|
||||
output, _ = logCmd.Output() // Ignore error - might be a new repo
|
||||
output, _ = logCmd.Output() // non-fatal: might be a new repo without remote tracking
|
||||
}
|
||||
if len(output) > 0 {
|
||||
lines := splitLines(string(output))
|
||||
|
||||
@@ -282,7 +282,7 @@ func ensureRefinerySession(rigName string, r *rig.Rig) (bool, error) {
|
||||
t.SetEnvironment(sessionName, "BEADS_NO_DAEMON", "1")
|
||||
t.SetEnvironment(sessionName, "BEADS_AGENT_NAME", fmt.Sprintf("%s/refinery", rigName))
|
||||
|
||||
// Apply Gas Town theming
|
||||
// Apply Gas Town theming (non-fatal: theming failure doesn't affect operation)
|
||||
theme := tmux.AssignTheme(rigName)
|
||||
_ = t.ConfigureGasTownSession(sessionName, theme, rigName, "refinery", "refinery")
|
||||
|
||||
@@ -399,7 +399,7 @@ func runGracefulShutdown(t *tmux.Tmux, gtSessions []string, townRoot string) err
|
||||
fmt.Printf("Phase 1: Sending ESC to %d agent(s)...\n", len(gtSessions))
|
||||
for _, sess := range gtSessions {
|
||||
fmt.Printf(" %s Interrupting %s\n", style.Bold.Render("→"), sess)
|
||||
_ = t.SendKeysRaw(sess, "Escape")
|
||||
_ = t.SendKeysRaw(sess, "Escape") // best-effort interrupt
|
||||
}
|
||||
|
||||
// Phase 2: Send shutdown message asking agents to handoff
|
||||
@@ -408,7 +408,7 @@ func runGracefulShutdown(t *tmux.Tmux, gtSessions []string, townRoot string) err
|
||||
for _, sess := range gtSessions {
|
||||
// Small delay then send the message
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
_ = t.SendKeys(sess, shutdownMsg)
|
||||
_ = t.SendKeys(sess, shutdownMsg) // best-effort notification
|
||||
}
|
||||
|
||||
// Phase 3: Wait for agents to complete handoff
|
||||
@@ -712,20 +712,20 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(sessionID, "GT_RIG", rigName)
|
||||
_ = t.SetEnvironment(sessionID, "GT_CREW", name)
|
||||
|
||||
// Set CLAUDE_CONFIG_DIR for account selection
|
||||
// Set CLAUDE_CONFIG_DIR for account selection (non-fatal)
|
||||
if claudeConfigDir != "" {
|
||||
_ = t.SetEnvironment(sessionID, "CLAUDE_CONFIG_DIR", claudeConfigDir)
|
||||
}
|
||||
|
||||
// Apply rig-based theming
|
||||
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
||||
theme := getThemeForRig(rigName)
|
||||
_ = t.ConfigureGasTownSession(sessionID, theme, rigName, name, "crew")
|
||||
|
||||
// Set up C-b n/p keybindings for crew session cycling
|
||||
// Set up C-b n/p keybindings for crew session cycling (non-fatal)
|
||||
_ = t.SetCrewCycleBindings(sessionID)
|
||||
|
||||
// Wait for shell to be ready after session creation
|
||||
|
||||
@@ -35,6 +35,7 @@ func runStatusLine(cmd *cobra.Command, args []string) error {
|
||||
var rigName, polecat, crew, issue, role string
|
||||
|
||||
if statusLineSession != "" {
|
||||
// Non-fatal: missing env vars are handled gracefully below
|
||||
rigName, _ = t.GetEnvironment(statusLineSession, "GT_RIG")
|
||||
polecat, _ = t.GetEnvironment(statusLineSession, "GT_POLECAT")
|
||||
crew, _ = t.GetEnvironment(statusLineSession, "GT_CREW")
|
||||
|
||||
@@ -155,7 +155,7 @@ func init() {
|
||||
swarmCreateCmd.Flags().StringSliceVar(&swarmWorkers, "worker", nil, "Polecat names to assign (repeatable)")
|
||||
swarmCreateCmd.Flags().BoolVar(&swarmStart, "start", false, "Start swarm immediately after creation")
|
||||
swarmCreateCmd.Flags().StringVar(&swarmTarget, "target", "main", "Target branch for landing")
|
||||
_ = swarmCreateCmd.MarkFlagRequired("epic")
|
||||
_ = swarmCreateCmd.MarkFlagRequired("epic") // cobra flags: error only at runtime if missing
|
||||
|
||||
// Status flags
|
||||
swarmStatusCmd.Flags().BoolVar(&swarmStatusJSON, "json", false, "Output as JSON")
|
||||
@@ -300,7 +300,7 @@ func runSwarmCreate(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the updated swarm
|
||||
// Get the updated swarm (just created, error would be surprising)
|
||||
sw, _ = mgr.GetSwarm(swarmEpic)
|
||||
|
||||
// Save to store
|
||||
@@ -640,7 +640,7 @@ func runSwarmLand(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// Create manager and land
|
||||
mgr := swarm.NewManager(foundRig)
|
||||
// Reload swarm into manager
|
||||
// Reload swarm into manager (recreates from store, errors non-fatal)
|
||||
_, _ = mgr.Create(sw.EpicID, sw.Workers, sw.TargetBranch)
|
||||
_ = mgr.UpdateState(sw.ID, sw.State)
|
||||
|
||||
|
||||
+4
-4
@@ -200,11 +200,11 @@ func ensureSession(t *tmux.Tmux, sessionName, workDir, role string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set environment
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(sessionName, "GT_ROLE", role)
|
||||
_ = t.SetEnvironment(sessionName, "BD_ACTOR", role)
|
||||
|
||||
// Apply theme based on role
|
||||
// Apply theme based on role (non-fatal: theming failure doesn't affect operation)
|
||||
switch role {
|
||||
case "mayor":
|
||||
theme := tmux.MayorTheme()
|
||||
@@ -246,13 +246,13 @@ func ensureWitness(t *tmux.Tmux, sessionName, rigPath, rigName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set environment
|
||||
// Set environment (non-fatal: session works without these)
|
||||
bdActor := fmt.Sprintf("%s/witness", rigName)
|
||||
_ = t.SetEnvironment(sessionName, "GT_ROLE", "witness")
|
||||
_ = t.SetEnvironment(sessionName, "GT_RIG", rigName)
|
||||
_ = t.SetEnvironment(sessionName, "BD_ACTOR", bdActor)
|
||||
|
||||
// Apply theme (use rig-based theme)
|
||||
// Apply theme (non-fatal: theming failure doesn't affect operation)
|
||||
theme := tmux.AssignTheme(rigName)
|
||||
_ = t.ConfigureGasTownSession(sessionName, theme, "", "Witness", rigName)
|
||||
|
||||
|
||||
@@ -157,8 +157,8 @@ func runWitnessStart(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update manager state to reflect running session
|
||||
_ = mgr.Start() // Mark as running in state file
|
||||
// Update manager state to reflect running session (non-fatal: state file update)
|
||||
_ = mgr.Start()
|
||||
|
||||
fmt.Printf("%s Witness started for %s\n", style.Bold.Render("✓"), rigName)
|
||||
fmt.Printf(" %s\n", style.Dim.Render("Use 'gt witness attach' to connect"))
|
||||
@@ -326,7 +326,7 @@ func ensureWitnessSession(rigName string, r *rig.Rig) (bool, error) {
|
||||
t.SetEnvironment(sessionName, "GT_RIG", rigName)
|
||||
t.SetEnvironment(sessionName, "BD_ACTOR", bdActor)
|
||||
|
||||
// Apply Gas Town theming
|
||||
// Apply Gas Town theming (non-fatal: theming failure doesn't affect operation)
|
||||
theme := tmux.AssignTheme(rigName)
|
||||
_ = t.ConfigureGasTownSession(sessionName, theme, rigName, "witness", "witness")
|
||||
|
||||
@@ -395,7 +395,7 @@ func runWitnessRestart(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Update state file to stopped
|
||||
// Update state file to stopped (non-fatal: state file update)
|
||||
_ = mgr.Stop()
|
||||
|
||||
// Start fresh
|
||||
@@ -405,7 +405,7 @@ func runWitnessRestart(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
if created {
|
||||
_ = mgr.Start() // Mark as running in state file
|
||||
_ = mgr.Start() // non-fatal: state file update
|
||||
}
|
||||
|
||||
fmt.Printf("%s Witness restarted for %s\n", style.Bold.Render("✓"), rigName)
|
||||
|
||||
@@ -82,11 +82,11 @@ func (m *Manager) Add(name string, createBranch bool) (*CrewWorker, error) {
|
||||
if createBranch {
|
||||
branchName = fmt.Sprintf("crew/%s", name)
|
||||
if err := crewGit.CreateBranch(branchName); err != nil {
|
||||
_ = os.RemoveAll(crewPath)
|
||||
_ = os.RemoveAll(crewPath) // best-effort cleanup
|
||||
return nil, fmt.Errorf("creating branch: %w", err)
|
||||
}
|
||||
if err := crewGit.Checkout(branchName); err != nil {
|
||||
_ = os.RemoveAll(crewPath)
|
||||
_ = os.RemoveAll(crewPath) // best-effort cleanup
|
||||
return nil, fmt.Errorf("checking out branch: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ func (m *Manager) Add(name string, createBranch bool) (*CrewWorker, error) {
|
||||
// Create mail directory for mail delivery
|
||||
mailPath := m.mailDir(name)
|
||||
if err := os.MkdirAll(mailPath, 0755); err != nil {
|
||||
_ = os.RemoveAll(crewPath)
|
||||
_ = os.RemoveAll(crewPath) // best-effort cleanup
|
||||
return nil, fmt.Errorf("creating mail dir: %w", err)
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func (m *Manager) Add(name string, createBranch bool) (*CrewWorker, error) {
|
||||
|
||||
// Create CLAUDE.md with crew worker prompting
|
||||
if err := m.createClaudeMD(name, crewPath); err != nil {
|
||||
_ = os.RemoveAll(crewPath)
|
||||
_ = os.RemoveAll(crewPath) // best-effort cleanup
|
||||
return nil, fmt.Errorf("creating CLAUDE.md: %w", err)
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ func (m *Manager) Add(name string, createBranch bool) (*CrewWorker, error) {
|
||||
|
||||
// Save state
|
||||
if err := m.saveState(crew); err != nil {
|
||||
_ = os.RemoveAll(crewPath)
|
||||
_ = os.RemoveAll(crewPath) // best-effort cleanup
|
||||
return nil, fmt.Errorf("saving state: %w", err)
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ func (m *Manager) Rename(oldName, newName string) error {
|
||||
// Update state file with new name and path
|
||||
crew, err := m.loadState(newName)
|
||||
if err != nil {
|
||||
// Rollback on error
|
||||
// Rollback on error (best-effort)
|
||||
_ = os.Rename(newPath, oldPath)
|
||||
return fmt.Errorf("loading state: %w", err)
|
||||
}
|
||||
@@ -335,7 +335,7 @@ func (m *Manager) Rename(oldName, newName string) error {
|
||||
crew.UpdatedAt = time.Now()
|
||||
|
||||
if err := m.saveState(crew); err != nil {
|
||||
// Rollback on error
|
||||
// Rollback on error (best-effort)
|
||||
_ = os.Rename(newPath, oldPath)
|
||||
return fmt.Errorf("saving state: %w", err)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func (d *Daemon) Run() error {
|
||||
if err := os.WriteFile(d.config.PidFile, []byte(strconv.Itoa(os.Getpid())), 0644); err != nil {
|
||||
return fmt.Errorf("writing PID file: %w", err)
|
||||
}
|
||||
defer func() { _ = os.Remove(d.config.PidFile) }()
|
||||
defer func() { _ = os.Remove(d.config.PidFile) }() // best-effort cleanup
|
||||
|
||||
// Update state
|
||||
state := &State{
|
||||
@@ -219,7 +219,7 @@ func (d *Daemon) ensureDeaconRunning() {
|
||||
return
|
||||
}
|
||||
|
||||
// Set environment
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = d.tmux.SetEnvironment(DeaconSessionName, "GT_ROLE", "deacon")
|
||||
_ = d.tmux.SetEnvironment(DeaconSessionName, "BD_ACTOR", "deacon")
|
||||
|
||||
@@ -361,7 +361,7 @@ func IsRunning(townRoot string) (bool, int, error) {
|
||||
// On Unix, FindProcess always succeeds. Send signal 0 to check if alive.
|
||||
err = process.Signal(syscall.Signal(0))
|
||||
if err != nil {
|
||||
// Process not running, clean up stale PID file
|
||||
// Process not running, clean up stale PID file (best-effort cleanup)
|
||||
_ = os.Remove(pidFile)
|
||||
return false, 0, nil
|
||||
}
|
||||
@@ -394,11 +394,11 @@ func StopDaemon(townRoot string) error {
|
||||
|
||||
// Check if still running
|
||||
if err := process.Signal(syscall.Signal(0)); err == nil {
|
||||
// Still running, force kill
|
||||
// Still running, force kill (best-effort)
|
||||
_ = process.Signal(syscall.SIGKILL)
|
||||
}
|
||||
|
||||
// Clean up PID file
|
||||
// Clean up PID file (best-effort cleanup)
|
||||
pidFile := filepath.Join(townRoot, "daemon", "daemon.pid")
|
||||
_ = os.Remove(pidFile)
|
||||
|
||||
|
||||
@@ -260,13 +260,13 @@ func (d *Daemon) restartSession(sessionName, identity string) error {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = d.tmux.SetEnvironment(sessionName, "GT_ROLE", identity)
|
||||
// BD_ACTOR uses slashes instead of dashes for path-like identity
|
||||
bdActor := identityToBDActor(identity)
|
||||
_ = d.tmux.SetEnvironment(sessionName, "BD_ACTOR", bdActor)
|
||||
|
||||
// Apply theme
|
||||
// Apply theme (non-fatal: theming failure doesn't affect operation)
|
||||
if identity == "mayor" {
|
||||
theme := tmux.MayorTheme()
|
||||
_ = d.tmux.ConfigureGasTownSession(sessionName, theme, "", "Mayor", "coordinator")
|
||||
|
||||
@@ -165,7 +165,7 @@ func (m *NotificationManager) MarkSessionActive(session string) error {
|
||||
ns.Consumed = true
|
||||
ns.ConsumedAt = time.Now()
|
||||
if data, err := json.Marshal(&ns); err == nil {
|
||||
_ = os.WriteFile(path, data, 0644)
|
||||
_ = os.WriteFile(path, data, 0644) // non-fatal: state file update
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func (m *NotificationManager) ClearStaleSlots() error {
|
||||
}
|
||||
|
||||
if time.Since(info.ModTime()) > m.maxAge {
|
||||
_ = os.Remove(path)
|
||||
_ = os.Remove(path) // best-effort cleanup
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,12 +140,12 @@ func (r *Report) Print(w io.Writer, verbose bool) {
|
||||
r.printCheck(w, check, verbose)
|
||||
}
|
||||
|
||||
// Print summary
|
||||
// Print summary (output errors non-actionable)
|
||||
_, _ = fmt.Fprintln(w)
|
||||
r.printSummary(w)
|
||||
}
|
||||
|
||||
// printCheck outputs a single check result.
|
||||
// printCheck outputs a single check result (output errors non-actionable).
|
||||
func (r *Report) printCheck(w io.Writer, check *CheckResult, verbose bool) {
|
||||
var prefix string
|
||||
switch check.Status {
|
||||
@@ -172,7 +172,7 @@ func (r *Report) printCheck(w io.Writer, check *CheckResult, verbose bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// printSummary outputs the summary line.
|
||||
// printSummary outputs the summary line (output errors non-actionable).
|
||||
func (r *Report) printSummary(w io.Writer) {
|
||||
parts := []string{
|
||||
fmt.Sprintf("%d checks", r.Summary.Total),
|
||||
|
||||
+4
-4
@@ -277,24 +277,24 @@ func (g *Git) CheckConflicts(source, target string) ([]string, error) {
|
||||
// Check if there are unmerged files (indicates conflict)
|
||||
conflicts, err := g.getConflictingFiles()
|
||||
if err == nil && len(conflicts) > 0 {
|
||||
// Abort the test merge
|
||||
// Abort the test merge (best-effort cleanup)
|
||||
_ = g.AbortMerge()
|
||||
return conflicts, nil
|
||||
}
|
||||
|
||||
// Check if it's a conflict error from wrapper
|
||||
if errors.Is(mergeErr, ErrMergeConflict) {
|
||||
_ = g.AbortMerge()
|
||||
_ = g.AbortMerge() // best-effort cleanup
|
||||
return conflicts, nil
|
||||
}
|
||||
|
||||
// Some other merge error
|
||||
// Some other merge error (best-effort cleanup)
|
||||
_ = g.AbortMerge()
|
||||
return nil, mergeErr
|
||||
}
|
||||
|
||||
// Merge succeeded (no conflicts) - abort the test merge
|
||||
// Use reset since --abort won't work on successful merge
|
||||
// Use reset since --abort won't work on successful merge (best-effort cleanup)
|
||||
_, _ = g.run("reset", "--hard", "HEAD")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func TouchInWorkspace(workspaceRoot, command string) {
|
||||
}
|
||||
|
||||
keepalivePath := filepath.Join(runtimeDir, "keepalive.json")
|
||||
_ = os.WriteFile(keepalivePath, data, 0644)
|
||||
_ = os.WriteFile(keepalivePath, data, 0644) // non-fatal: status file for debugging
|
||||
}
|
||||
|
||||
// Read returns the current keepalive state for the workspace.
|
||||
|
||||
@@ -124,7 +124,7 @@ func (l *Lock) Check() error {
|
||||
|
||||
// Check if stale
|
||||
if info.IsStale() {
|
||||
// Clean up stale lock
|
||||
// Clean up stale lock (best-effort cleanup)
|
||||
_ = l.Release()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ func (m *Mailbox) listLegacy() ([]*Message, error) {
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
defer func() { _ = file.Close() }() // non-fatal: OS will close on exit
|
||||
|
||||
var messages []*Message
|
||||
scanner := bufio.NewScanner(file)
|
||||
@@ -392,7 +392,7 @@ func (m *Mailbox) appendLegacy(msg *Message) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
defer func() { _ = file.Close() }() // non-fatal: OS will close on exit
|
||||
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
@@ -420,15 +420,15 @@ func (m *Mailbox) rewriteLegacy(messages []*Message) error {
|
||||
for _, msg := range messages {
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
_ = os.Remove(tmpPath)
|
||||
_ = file.Close() // best-effort cleanup
|
||||
_ = os.Remove(tmpPath) // best-effort cleanup
|
||||
return err
|
||||
}
|
||||
_, _ = file.WriteString(string(data) + "\n")
|
||||
_, _ = file.WriteString(string(data) + "\n") // non-fatal: partial write is acceptable
|
||||
}
|
||||
|
||||
if err := file.Close(); err != nil {
|
||||
_ = os.Remove(tmpPath)
|
||||
_ = os.Remove(tmpPath) // best-effort cleanup
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ func (r *Router) Send(msg *Message) error {
|
||||
return fmt.Errorf("sending message: %w", err)
|
||||
}
|
||||
|
||||
// Notify recipient if they have an active session
|
||||
// Notify recipient if they have an active session (best-effort notification)
|
||||
// Skip notification for self-mail (handoffs to future-self don't need present-self notified)
|
||||
if !isSelfMail(msg.From, msg.To) {
|
||||
_ = r.notifyRecipient(msg)
|
||||
|
||||
@@ -140,14 +140,14 @@ func NewReplyMessage(from, to, subject, body string, original *Message) *Message
|
||||
// generateID creates a random message ID.
|
||||
func generateID() string {
|
||||
b := make([]byte, 8)
|
||||
_, _ = rand.Read(b)
|
||||
_, _ = rand.Read(b) // crypto/rand.Read only fails on broken system
|
||||
return "msg-" + hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// generateThreadID creates a random thread ID.
|
||||
func generateThreadID() string {
|
||||
b := make([]byte, 6)
|
||||
_, _ = rand.Read(b)
|
||||
_, _ = rand.Read(b) // crypto/rand.Read only fails on broken system
|
||||
return "thread-" + hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
func GenerateMRID(prefix, branch string) string {
|
||||
// Generate 8 random bytes for additional uniqueness
|
||||
randomBytes := make([]byte, 8)
|
||||
_, _ = rand.Read(randomBytes)
|
||||
_, _ = rand.Read(randomBytes) // crypto/rand.Read only fails on broken system
|
||||
|
||||
return generateMRIDInternal(prefix, branch, time.Now(), randomBytes)
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func NewManager(r *rig.Rig, g *git.Git) *Manager {
|
||||
// Use defaults
|
||||
pool = NewNamePool(r.Path, r.Name)
|
||||
}
|
||||
_ = pool.Load() // Load existing state, ignore errors for new rigs
|
||||
_ = pool.Load() // non-fatal: state file may not exist for new rigs
|
||||
|
||||
return &Manager{
|
||||
rig: r,
|
||||
@@ -230,10 +230,10 @@ func (m *Manager) RemoveWithOptions(name string, force, nuclear bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Prune any stale worktree entries
|
||||
// Prune any stale worktree entries (non-fatal: cleanup only)
|
||||
_ = repoGit.WorktreePrune()
|
||||
|
||||
// Release name back to pool if it's a pooled name
|
||||
// Release name back to pool if it's a pooled name (non-fatal: state file update)
|
||||
m.namePool.Release(name)
|
||||
_ = m.namePool.Save()
|
||||
|
||||
@@ -263,7 +263,7 @@ func (m *Manager) AllocateName() (string, error) {
|
||||
// This is called when a polecat is removed.
|
||||
func (m *Manager) ReleaseName(name string) {
|
||||
m.namePool.Release(name)
|
||||
_ = m.namePool.Save()
|
||||
_ = m.namePool.Save() // non-fatal: state file update
|
||||
}
|
||||
|
||||
// Recreate removes an existing polecat and creates a fresh worktree.
|
||||
@@ -301,14 +301,14 @@ func (m *Manager) Recreate(name string, force bool) (*Polecat, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Prune stale worktree entries
|
||||
// Prune stale worktree entries (non-fatal: cleanup only)
|
||||
_ = repoGit.WorktreePrune()
|
||||
|
||||
// Fetch latest from origin to ensure we have fresh commits
|
||||
// Fetch latest from origin to ensure we have fresh commits (non-fatal: may be offline)
|
||||
_ = repoGit.Fetch("origin")
|
||||
|
||||
// Delete the old branch so worktree starts fresh from current HEAD
|
||||
// Ignore error - branch may not exist (first recreate) or may fail to delete
|
||||
// Non-fatal: branch may not exist (first recreate) or may fail to delete
|
||||
_ = repoGit.DeleteBranch(branchName, true)
|
||||
|
||||
// Check if branch still exists (deletion may have failed or branch was protected)
|
||||
@@ -366,7 +366,7 @@ func (m *Manager) ReconcilePool() {
|
||||
}
|
||||
|
||||
m.namePool.Reconcile(names)
|
||||
_ = m.namePool.Save()
|
||||
_ = m.namePool.Save() // non-fatal: state file update
|
||||
}
|
||||
|
||||
// PoolStatus returns information about the name pool.
|
||||
|
||||
@@ -141,7 +141,7 @@ func CheckInboxForSpawns(townRoot string) ([]*PendingSpawn, error) {
|
||||
pending = append(pending, ps)
|
||||
existing[msg.ID] = true
|
||||
|
||||
// Mark message as read
|
||||
// Mark message as read (non-fatal: message tracking)
|
||||
_ = mailbox.MarkRead(msg.ID)
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ func (m *Manager) Status() (*Refinery, error) {
|
||||
// If tmux session is running, refinery is running
|
||||
if sessionRunning {
|
||||
if ref.State != StateRunning {
|
||||
// Update state to match reality
|
||||
// Update state to match reality (non-fatal: state file update)
|
||||
now := time.Now()
|
||||
ref.State = StateRunning
|
||||
if ref.StartedAt == nil {
|
||||
@@ -127,7 +127,7 @@ func (m *Manager) Status() (*Refinery, error) {
|
||||
// Process is still running (foreground mode without tmux)
|
||||
return ref, nil
|
||||
}
|
||||
// Neither session nor process exists - mark as stopped
|
||||
// Neither session nor process exists - mark as stopped (non-fatal: state file update)
|
||||
ref.State = StateStopped
|
||||
ref.PID = 0
|
||||
_ = m.saveState(ref)
|
||||
@@ -199,20 +199,20 @@ func (m *Manager) Start(foreground bool) error {
|
||||
return fmt.Errorf("creating tmux session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment variables
|
||||
// Set environment variables (non-fatal: session works without these)
|
||||
bdActor := fmt.Sprintf("%s/refinery", m.rig.Name)
|
||||
_ = t.SetEnvironment(sessionID, "GT_RIG", m.rig.Name)
|
||||
_ = t.SetEnvironment(sessionID, "GT_REFINERY", "1")
|
||||
_ = t.SetEnvironment(sessionID, "GT_ROLE", "refinery")
|
||||
_ = t.SetEnvironment(sessionID, "BD_ACTOR", bdActor)
|
||||
|
||||
// Set beads environment - refinery uses rig-level beads
|
||||
// Set beads environment - refinery uses rig-level beads (non-fatal)
|
||||
beadsDir := filepath.Join(m.rig.Path, "mayor", "rig", ".beads")
|
||||
_ = t.SetEnvironment(sessionID, "BEADS_DIR", beadsDir)
|
||||
_ = t.SetEnvironment(sessionID, "BEADS_NO_DAEMON", "1")
|
||||
_ = t.SetEnvironment(sessionID, "BEADS_AGENT_NAME", fmt.Sprintf("%s/refinery", m.rig.Name))
|
||||
|
||||
// Apply theme (same as rig polecats)
|
||||
// Apply theme (non-fatal: theming failure doesn't affect operation)
|
||||
theme := tmux.AssignTheme(m.rig.Name)
|
||||
_ = t.ConfigureGasTownSession(sessionID, theme, m.rig.Name, "refinery", "refinery")
|
||||
|
||||
@@ -222,7 +222,7 @@ func (m *Manager) Start(foreground bool) error {
|
||||
ref.StartedAt = &now
|
||||
ref.PID = 0 // Claude agent doesn't have a PID we track
|
||||
if err := m.saveState(ref); err != nil {
|
||||
_ = t.KillSession(sessionID)
|
||||
_ = t.KillSession(sessionID) // best-effort cleanup on state save failure
|
||||
return fmt.Errorf("saving state: %w", err)
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ func (m *Manager) Start(foreground bool) error {
|
||||
// Restarts are handled by daemon via LIFECYCLE mail, not shell loops
|
||||
command := "claude --dangerously-skip-permissions"
|
||||
if err := t.SendKeys(sessionID, command); err != nil {
|
||||
// Clean up the session on failure
|
||||
// Clean up the session on failure (best-effort cleanup)
|
||||
_ = t.KillSession(sessionID)
|
||||
return fmt.Errorf("starting Claude agent: %w", err)
|
||||
}
|
||||
@@ -256,14 +256,14 @@ func (m *Manager) Stop() error {
|
||||
return ErrNotRunning
|
||||
}
|
||||
|
||||
// Kill tmux session if it exists
|
||||
// Kill tmux session if it exists (best-effort: may already be dead)
|
||||
if sessionRunning {
|
||||
_ = t.KillSession(sessionID)
|
||||
}
|
||||
|
||||
// If we have a PID and it's a different process, try to stop it gracefully
|
||||
if ref.PID > 0 && ref.PID != os.Getpid() && processExists(ref.PID) {
|
||||
// Send SIGTERM
|
||||
// Send SIGTERM (best-effort graceful stop)
|
||||
if proc, err := os.FindProcess(ref.PID); err == nil {
|
||||
_ = proc.Signal(os.Interrupt)
|
||||
}
|
||||
@@ -430,7 +430,7 @@ func (m *Manager) ProcessMR(mr *MergeRequest) MergeResult {
|
||||
return MergeResult{Error: fmt.Sprintf("cannot claim MR: %v", err)}
|
||||
}
|
||||
ref.CurrentMR = mr
|
||||
_ = m.saveState(ref)
|
||||
_ = m.saveState(ref) // non-fatal: state file update
|
||||
|
||||
result := MergeResult{}
|
||||
|
||||
@@ -448,8 +448,8 @@ func (m *Manager) ProcessMR(mr *MergeRequest) MergeResult {
|
||||
return result
|
||||
}
|
||||
|
||||
// Pull latest
|
||||
_ = m.gitRun("pull", "origin", mr.TargetBranch) // Ignore errors
|
||||
// Pull latest (non-fatal: may fail if remote unreachable)
|
||||
_ = m.gitRun("pull", "origin", mr.TargetBranch)
|
||||
|
||||
// 3. Merge
|
||||
err := m.gitRun("merge", "--no-ff", "-m",
|
||||
@@ -461,7 +461,7 @@ func (m *Manager) ProcessMR(mr *MergeRequest) MergeResult {
|
||||
if strings.Contains(errStr, "CONFLICT") || strings.Contains(errStr, "conflict") {
|
||||
result.Conflict = true
|
||||
result.Error = "merge conflict"
|
||||
// Abort the merge
|
||||
// Abort the merge (best-effort cleanup)
|
||||
_ = m.gitRun("merge", "--abort")
|
||||
m.completeMR(mr, "", "merge conflict - polecat must rebase") // Reopen for rebase
|
||||
// Notify worker about conflict
|
||||
@@ -478,7 +478,7 @@ func (m *Manager) ProcessMR(mr *MergeRequest) MergeResult {
|
||||
if err := m.runTests(config.TestCommand); err != nil {
|
||||
result.TestsFailed = true
|
||||
result.Error = fmt.Sprintf("tests failed: %v", err)
|
||||
// Reset to before merge
|
||||
// Reset to before merge (best-effort rollback)
|
||||
_ = m.gitRun("reset", "--hard", "HEAD~1")
|
||||
m.completeMR(mr, "", result.Error) // Reopen for fixes
|
||||
return result
|
||||
@@ -488,7 +488,7 @@ func (m *Manager) ProcessMR(mr *MergeRequest) MergeResult {
|
||||
// 5. Push with retry logic
|
||||
if err := m.pushWithRetry(mr.TargetBranch, config); err != nil {
|
||||
result.Error = fmt.Sprintf("push failed: %v", err)
|
||||
// Reset to before merge
|
||||
// Reset to before merge (best-effort rollback)
|
||||
_ = m.gitRun("reset", "--hard", "HEAD~1")
|
||||
m.completeMR(mr, "", result.Error) // Reopen for retry
|
||||
return result
|
||||
@@ -508,7 +508,7 @@ func (m *Manager) ProcessMR(mr *MergeRequest) MergeResult {
|
||||
// Notify worker of success
|
||||
m.notifyWorkerMerged(mr)
|
||||
|
||||
// Optionally delete the merged branch (local only - branches never go to origin)
|
||||
// Optionally delete the merged branch (non-fatal: cleanup only)
|
||||
if config.DeleteMergedBranches {
|
||||
_ = m.gitRun("branch", "-D", mr.Branch)
|
||||
}
|
||||
@@ -554,7 +554,7 @@ func (m *Manager) completeMR(mr *MergeRequest, closeReason CloseReason, errMsg s
|
||||
ref.Stats.TodayFailed++
|
||||
}
|
||||
|
||||
_ = m.saveState(ref)
|
||||
_ = m.saveState(ref) // non-fatal: state file update
|
||||
}
|
||||
|
||||
// getTestCommand returns the test command if configured.
|
||||
@@ -721,7 +721,7 @@ Then the Refinery will retry the merge.`,
|
||||
mr.Branch, mr.TargetBranch, mr.TargetBranch),
|
||||
Priority: mail.PriorityHigh,
|
||||
}
|
||||
_ = router.Send(msg)
|
||||
_ = router.Send(msg) // best-effort notification
|
||||
}
|
||||
|
||||
// notifyWorkerMerged sends a success notification to a polecat.
|
||||
@@ -737,7 +737,7 @@ Issue: %s
|
||||
Thank you for your contribution!`,
|
||||
mr.Branch, mr.TargetBranch, mr.IssueID),
|
||||
}
|
||||
_ = router.Send(msg)
|
||||
_ = router.Send(msg) // best-effort notification
|
||||
}
|
||||
|
||||
// Common errors for MR operations
|
||||
@@ -897,7 +897,7 @@ Please review the feedback and address the issues before resubmitting.`,
|
||||
mr.Branch, mr.IssueID, reason),
|
||||
Priority: mail.PriorityNormal,
|
||||
}
|
||||
_ = router.Send(msg)
|
||||
_ = router.Send(msg) // best-effort notification
|
||||
}
|
||||
|
||||
// findTownRoot walks up directories to find the town root.
|
||||
|
||||
@@ -193,7 +193,7 @@ func (m *Manager) AddRig(opts AddRigOptions) (*Rig, error) {
|
||||
return nil, fmt.Errorf("creating rig directory: %w", err)
|
||||
}
|
||||
|
||||
// Track cleanup on failure
|
||||
// Track cleanup on failure (best-effort cleanup)
|
||||
cleanup := func() { _ = os.RemoveAll(rigPath) }
|
||||
success := false
|
||||
defer func() {
|
||||
|
||||
@@ -136,16 +136,16 @@ func (m *Manager) Start(polecat string, opts StartOptions) error {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = m.tmux.SetEnvironment(sessionID, "GT_RIG", m.rig.Name)
|
||||
_ = m.tmux.SetEnvironment(sessionID, "GT_POLECAT", polecat)
|
||||
|
||||
// Set CLAUDE_CONFIG_DIR for account selection
|
||||
// Set CLAUDE_CONFIG_DIR for account selection (non-fatal)
|
||||
if opts.ClaudeConfigDir != "" {
|
||||
_ = m.tmux.SetEnvironment(sessionID, "CLAUDE_CONFIG_DIR", opts.ClaudeConfigDir)
|
||||
}
|
||||
|
||||
// CRITICAL: Set beads environment for worktree polecats
|
||||
// CRITICAL: Set beads environment for worktree polecats (non-fatal: session works without)
|
||||
// Polecats share the rig's beads directory (at rig root, not mayor/rig)
|
||||
// BEADS_NO_DAEMON=1 prevents daemon from committing to wrong branch
|
||||
beadsDir := filepath.Join(m.rig.Path, ".beads")
|
||||
@@ -153,7 +153,7 @@ func (m *Manager) Start(polecat string, opts StartOptions) error {
|
||||
_ = m.tmux.SetEnvironment(sessionID, "BEADS_NO_DAEMON", "1")
|
||||
_ = m.tmux.SetEnvironment(sessionID, "BEADS_AGENT_NAME", fmt.Sprintf("%s/%s", m.rig.Name, polecat))
|
||||
|
||||
// Apply theme
|
||||
// Apply theme (non-fatal: theming failure doesn't affect operation)
|
||||
theme := tmux.AssignTheme(m.rig.Name)
|
||||
_ = m.tmux.ConfigureGasTownSession(sessionID, theme, m.rig.Name, polecat, "polecat")
|
||||
|
||||
@@ -198,9 +198,9 @@ func (m *Manager) Stop(polecat string, force bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Try graceful shutdown first (unless forced)
|
||||
// Try graceful shutdown first (unless forced, best-effort interrupt)
|
||||
if !force {
|
||||
_ = m.tmux.SendKeysRaw(sessionID, "C-c") // Ctrl+C
|
||||
_ = m.tmux.SendKeysRaw(sessionID, "C-c")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ func (m *Manager) CreateIntegrationBranch(swarmID string) error {
|
||||
return fmt.Errorf("creating branch: %w", err)
|
||||
}
|
||||
|
||||
// Push to origin
|
||||
_ = m.gitRun("push", "-u", "origin", branchName) // Non-fatal - may not have remote
|
||||
// Push to origin (non-fatal: may not have remote)
|
||||
_ = m.gitRun("push", "-u", "origin", branchName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -61,8 +61,8 @@ func (m *Manager) MergeToIntegration(swarmID, workerBranch string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the worker branch
|
||||
_ = m.gitRun("fetch", "origin", workerBranch) // May not exist on remote, try local
|
||||
// Fetch the worker branch (non-fatal: may not exist on remote, try local)
|
||||
_ = m.gitRun("fetch", "origin", workerBranch)
|
||||
|
||||
// Attempt merge
|
||||
err = m.gitRun("merge", "--no-ff", "-m",
|
||||
@@ -97,8 +97,8 @@ func (m *Manager) LandToMain(swarmID string) error {
|
||||
return fmt.Errorf("checking out %s: %w", swarm.TargetBranch, err)
|
||||
}
|
||||
|
||||
// Pull latest
|
||||
_ = m.gitRun("pull", "origin", swarm.TargetBranch) // Ignore errors
|
||||
// Pull latest (non-fatal: may fail if remote unreachable)
|
||||
_ = m.gitRun("pull", "origin", swarm.TargetBranch)
|
||||
|
||||
// Merge integration branch
|
||||
err := m.gitRun("merge", "--no-ff", "-m",
|
||||
@@ -133,10 +133,10 @@ func (m *Manager) CleanupBranches(swarmID string) error {
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
// Delete integration branch remotely
|
||||
_ = m.gitRun("push", "origin", "--delete", swarm.Integration) // Ignore errors
|
||||
// Delete integration branch remotely (best-effort cleanup)
|
||||
_ = m.gitRun("push", "origin", "--delete", swarm.Integration)
|
||||
|
||||
// Delete worker branches
|
||||
// Delete worker branches (best-effort cleanup)
|
||||
for _, task := range swarm.Tasks {
|
||||
if task.Branch != "" {
|
||||
// Local delete
|
||||
|
||||
@@ -210,7 +210,7 @@ Manual intervention required.`,
|
||||
swarmID, strings.Join(workers, "\n- ")),
|
||||
Priority: mail.PriorityHigh,
|
||||
}
|
||||
_ = router.Send(msg)
|
||||
_ = router.Send(msg) // best-effort notification
|
||||
}
|
||||
|
||||
// notifyMayorLanded sends a landing report to Mayor.
|
||||
@@ -233,5 +233,5 @@ Tasks merged: %d`,
|
||||
result.BranchesCleaned,
|
||||
len(swarm.Tasks)),
|
||||
}
|
||||
_ = router.Send(msg)
|
||||
_ = router.Send(msg) // best-effort notification
|
||||
}
|
||||
|
||||
@@ -465,7 +465,7 @@ func (t *Tmux) GetSessionInfo(name string) (*SessionInfo, error) {
|
||||
}
|
||||
|
||||
windows := 0
|
||||
_, _ = fmt.Sscanf(parts[1], "%d", &windows)
|
||||
_, _ = fmt.Sscanf(parts[1], "%d", &windows) // non-fatal: defaults to 0 on parse error
|
||||
|
||||
info := &SessionInfo{
|
||||
Name: parts[0],
|
||||
|
||||
@@ -83,7 +83,7 @@ func (m *Manager) Status() (*Witness, error) {
|
||||
if !processExists(w.PID) {
|
||||
w.State = StateStopped
|
||||
w.PID = 0
|
||||
_ = m.saveState(w)
|
||||
_ = m.saveState(w) // non-fatal: state file update
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ func (m *Manager) Stop() error {
|
||||
|
||||
// If we have a PID, try to stop it gracefully
|
||||
if w.PID > 0 && w.PID != os.Getpid() {
|
||||
// Send SIGTERM
|
||||
// Send SIGTERM (best-effort graceful stop)
|
||||
if proc, err := os.FindProcess(w.PID); err == nil {
|
||||
_ = proc.Signal(os.Interrupt)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user