fix(ci): resolve lint errors and test failures

- Fix errcheck: handle watcher.Close() and Set() return values
- Fix unparam: remove always-nil error from NewActivityWatcher
- Fix unparam: remove unused sinceTime param, delete dead code
- Fix version mismatch: update MCP __init__.py to 0.48.0
- Fix routing tests: change CWD so routing can find town root

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/lydia
2026-01-20 20:50:43 -08:00
committed by Steve Yegge
parent 8807a171d3
commit 83e3c75635
5 changed files with 45 additions and 77 deletions

View File

@@ -185,13 +185,8 @@ func runActivityFollow(sinceTime time.Time) {
// Create filesystem watcher for near-instant wake-up
// Falls back to polling internally if fsnotify fails
beadsDir := filepath.Dir(dbPath)
watcher, err := NewActivityWatcher(beadsDir, activityInterval)
if err != nil {
// Watcher creation failed entirely - fall back to legacy polling
runActivityFollowPolling(sinceTime, lastPoll)
return
}
defer watcher.Close()
watcher := NewActivityWatcher(beadsDir, activityInterval)
defer func() { _ = watcher.Close() }()
// Start watching
watcher.Start(rootCtx)
@@ -261,69 +256,6 @@ func runActivityFollow(sinceTime time.Time) {
}
}
// runActivityFollowPolling is the legacy polling-based follow mode.
// Used as fallback when ActivityWatcher cannot be created.
func runActivityFollowPolling(sinceTime time.Time, lastPoll time.Time) {
ticker := time.NewTicker(activityInterval)
defer ticker.Stop()
consecutiveFailures := 0
const failureWarningThreshold = 5
lastWarningTime := time.Time{}
for {
select {
case <-rootCtx.Done():
return
case <-ticker.C:
newEvents, err := fetchMutations(lastPoll)
if err != nil {
consecutiveFailures++
if consecutiveFailures >= failureWarningThreshold {
if time.Since(lastWarningTime) >= 30*time.Second {
if jsonOutput {
errorEvent := map[string]interface{}{
"type": "error",
"message": fmt.Sprintf("daemon unreachable (%d failures)", consecutiveFailures),
"timestamp": time.Now().Format(time.RFC3339),
}
data, _ := json.Marshal(errorEvent)
fmt.Fprintln(os.Stderr, string(data))
} else {
timestamp := time.Now().Format("15:04:05")
fmt.Fprintf(os.Stderr, "[%s] %s daemon unreachable (%d consecutive failures)\n",
timestamp, ui.RenderWarn("!"), consecutiveFailures)
}
lastWarningTime = time.Now()
}
}
continue
}
if consecutiveFailures > 0 {
if consecutiveFailures >= failureWarningThreshold && !jsonOutput {
timestamp := time.Now().Format("15:04:05")
fmt.Fprintf(os.Stderr, "[%s] %s daemon reconnected\n", timestamp, ui.RenderPass("✓"))
}
consecutiveFailures = 0
}
newEvents = filterEvents(newEvents)
for _, e := range newEvents {
if jsonOutput {
data, _ := json.Marshal(formatEvent(e))
fmt.Println(string(data))
} else {
printEvent(e)
}
if e.Timestamp.After(lastPoll) {
lastPoll = e.Timestamp
}
}
}
}
}
// fetchMutations retrieves mutations from the daemon
func fetchMutations(since time.Time) ([]rpc.MutationEvent, error) {
var sinceMillis int64

View File

@@ -30,7 +30,7 @@ type ActivityWatcher struct {
// Watches the dolt noms directory for commits, falling back to polling if fsnotify fails.
// The beadsDir should be the .beads directory path.
// The pollInterval is used for polling fallback mode.
func NewActivityWatcher(beadsDir string, pollInterval time.Duration) (*ActivityWatcher, error) {
func NewActivityWatcher(beadsDir string, pollInterval time.Duration) *ActivityWatcher {
aw := &ActivityWatcher{
pollInterval: pollInterval,
events: make(chan struct{}, 1), // Buffered to avoid blocking
@@ -74,7 +74,7 @@ func NewActivityWatcher(beadsDir string, pollInterval time.Duration) (*ActivityW
if err != nil {
// Fall back to polling mode
aw.pollingMode = true
return aw, nil
return aw
}
// Add watches for each path
@@ -91,11 +91,11 @@ func NewActivityWatcher(beadsDir string, pollInterval time.Duration) (*ActivityW
// No paths could be watched, fall back to polling
_ = watcher.Close()
aw.pollingMode = true
return aw, nil
return aw
}
aw.watcher = watcher
return aw, nil
return aw
}
// Events returns the channel that receives wake-up signals when changes are detected.

View File

@@ -12,6 +12,8 @@ import (
// TestAgentStateWithRouting tests that bd agent state respects routes.jsonl
// for cross-repo agent resolution. This is a regression test for the bug where
// bd agent state failed to find agents in routed databases while bd show worked.
//
// NOTE: This test uses os.Chdir and cannot run in parallel with other tests.
func TestAgentStateWithRouting(t *testing.T) {
ctx := context.Background()
@@ -73,6 +75,16 @@ func TestAgentStateWithRouting(t *testing.T) {
dbPath = townDBPath
t.Cleanup(func() { dbPath = oldDbPath })
// Change to tmpDir so routing can find town root via CWD
oldWd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working directory: %v", err)
}
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to change to temp directory: %v", err)
}
t.Cleanup(func() { _ = os.Chdir(oldWd) })
// Test the routed resolution
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-testrig-polecat-test")
if err != nil {
@@ -115,6 +127,8 @@ func TestNeedsRoutingFunction(t *testing.T) {
}
// TestAgentHeartbeatWithRouting tests that bd agent heartbeat respects routes.jsonl
//
// NOTE: This test uses os.Chdir and cannot run in parallel with other tests.
func TestAgentHeartbeatWithRouting(t *testing.T) {
ctx := context.Background()
@@ -167,6 +181,16 @@ func TestAgentHeartbeatWithRouting(t *testing.T) {
dbPath = townDBPath
t.Cleanup(func() { dbPath = oldDbPath })
// Change to tmpDir so routing can find town root via CWD
oldWd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working directory: %v", err)
}
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to change to temp directory: %v", err)
}
t.Cleanup(func() { _ = os.Chdir(oldWd) })
// Test that we can resolve the agent from the town directory
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-test-witness")
if err != nil {
@@ -189,6 +213,8 @@ func TestAgentHeartbeatWithRouting(t *testing.T) {
}
// TestAgentShowWithRouting tests that bd agent show respects routes.jsonl
//
// NOTE: This test uses os.Chdir and cannot run in parallel with other tests.
func TestAgentShowWithRouting(t *testing.T) {
ctx := context.Background()
@@ -241,6 +267,16 @@ func TestAgentShowWithRouting(t *testing.T) {
dbPath = townDBPath
t.Cleanup(func() { dbPath = oldDbPath })
// Change to tmpDir so routing can find town root via CWD
oldWd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working directory: %v", err)
}
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to change to temp directory: %v", err)
}
t.Cleanup(func() { _ = os.Chdir(oldWd) })
// Test that we can resolve the agent from the town directory
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-myrig-crew-alice")
if err != nil {

View File

@@ -23,8 +23,8 @@ Examples:
parentID := args[0]
// Set the parent flag on listCmd, run it, then reset
listCmd.Flags().Set("parent", parentID)
defer listCmd.Flags().Set("parent", "")
_ = listCmd.Flags().Set("parent", parentID)
defer func() { _ = listCmd.Flags().Set("parent", "") }()
listCmd.Run(listCmd, []string{})
},
}

View File

@@ -4,4 +4,4 @@ This package provides an MCP (Model Context Protocol) server that exposes
beads (bd) issue tracker functionality to MCP Clients.
"""
__version__ = "0.47.2"
__version__ = "0.48.0"