Implements all three follow-up phases for sandbox environment support: **Phase 1 (bd-9nw): Documentation** ✅ - Comprehensive sandbox troubleshooting section in TROUBLESHOOTING.md - Detailed symptoms, root causes, and escape hatches - Step-by-step troubleshooting workflow - Comparison table for --sandbox, --force, and --allow-stale flags - Global flags section added to CLI_REFERENCE.md - Documents --sandbox, --allow-stale, and --force flags - Usage examples and when to use each flag - GitHub issue #353 comment with immediate workarounds **Phase 2 (bd-u3t): Sandbox Auto-Detection** ✅ - Automatic sandbox detection using syscall.Kill permission checks - cmd/bd/sandbox_unix.go: Unix/Linux/macOS implementation - cmd/bd/sandbox_windows.go: Windows stub (conservative approach) - cmd/bd/sandbox_test.go: Comprehensive test coverage - Auto-enables sandbox mode when detected - Shows: "ℹ️ Sandbox detected, using direct mode" - Respects explicit --sandbox or --no-daemon flags - Updated documentation to reflect auto-detection (v0.21.1+) **Phase 3 (bd-e0o): Enhanced Daemon Robustness** ✅ - Permission-aware process checks in cmd/bd/daemon_unix.go - Correctly handles EPERM (operation not permitted) from syscall.Kill - Treats EPERM as "process exists but not signable" = running - Prevents false negatives in sandboxed environments - Metadata health check in cmd/bd/daemon_event_loop.go - Periodic verification that metadata is accessible - Helps detect external import operations (bd import --force) - Non-fatal logging for diagnostics - Comprehensive test suite in cmd/bd/daemon_unix_test.go - Self-check, init process, nonexistent process, parent process tests **Impact:** - Codex users: No manual intervention needed, auto-detected - Stuck states: Three escape hatches (--sandbox, --force, --allow-stale) - Daemon robustness: Handles permission-restricted environments gracefully - All three follow-up issues (bd-9nw, bd-u3t, bd-e0o) closed **Files changed:** - cmd/bd/main.go: Auto-detection logic in PersistentPreRun - cmd/bd/sandbox_unix.go: Unix sandbox detection (new) - cmd/bd/sandbox_windows.go: Windows sandbox detection stub (new) - cmd/bd/sandbox_test.go: Sandbox detection tests (new) - cmd/bd/daemon_unix.go: Permission-aware isProcessRunning() - cmd/bd/daemon_unix_test.go: Process check tests (new) - cmd/bd/daemon_event_loop.go: Metadata health check - docs/TROUBLESHOOTING.md: Comprehensive sandbox section - docs/CLI_REFERENCE.md: Global flags documentation Closes bd-9nw, bd-u3t, bd-e0o Related: GH #353 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
51 lines
1.6 KiB
Go
51 lines
1.6 KiB
Go
//go:build unix
|
|
|
|
package main
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
// TestIsProcessRunning_SelfCheck verifies that we can always detect our own process
|
|
func TestIsProcessRunning_SelfCheck(t *testing.T) {
|
|
myPID := os.Getpid()
|
|
if !isProcessRunning(myPID) {
|
|
t.Errorf("isProcessRunning(%d) returned false for our own PID", myPID)
|
|
}
|
|
}
|
|
|
|
// TestIsProcessRunning_Init verifies that PID 1 (init/systemd/launchd) is always running
|
|
func TestIsProcessRunning_Init(t *testing.T) {
|
|
// PID 1 should always be running on Unix systems
|
|
if !isProcessRunning(1) {
|
|
t.Errorf("isProcessRunning(1) returned false, but init/systemd should always be running")
|
|
}
|
|
}
|
|
|
|
// TestIsProcessRunning_NonexistentProcess verifies that we correctly detect dead processes
|
|
func TestIsProcessRunning_NonexistentProcess(t *testing.T) {
|
|
// Pick a PID that's very unlikely to exist (max PID on most systems is < 100000)
|
|
impossiblePID := 9999999
|
|
if isProcessRunning(impossiblePID) {
|
|
t.Errorf("isProcessRunning(%d) returned true for likely nonexistent PID", impossiblePID)
|
|
t.Logf("If this fails, the test PID may actually exist on this system")
|
|
}
|
|
}
|
|
|
|
// TestIsProcessRunning_ParentProcess verifies that we can detect our parent process
|
|
func TestIsProcessRunning_ParentProcess(t *testing.T) {
|
|
parentPID := os.Getppid()
|
|
if parentPID == 0 {
|
|
t.Skip("Parent PID is 0 (orphaned process), skipping test")
|
|
}
|
|
if parentPID == 1 {
|
|
t.Skip("Parent PID is 1 (adopted by init), skipping test")
|
|
}
|
|
|
|
// Our parent process should be running (it spawned us)
|
|
if !isProcessRunning(parentPID) {
|
|
t.Errorf("isProcessRunning(%d) returned false for our parent process", parentPID)
|
|
}
|
|
}
|