fix: prevent inherited BEADS_DIR from causing prefix mismatch (#321)
- Fix beads.run() to always explicitly set BEADS_DIR based on the working directory or explicit override - This prevents inherited environment variables (e.g., from mayor session with BEADS_DIR=/home/erik/gt/.beads) from causing prefix mismatch errors when creating agent beads for rigs - Update polecat manager to use NewWithBeadsDir for explicitness - Add comprehensive test coverage for BEADS_DIR routing and validation - Add SessionLister interface for deterministic orphan session testing Root cause: When BEADS_DIR was set in the parent environment, all bd commands used the town database (hq- prefix) instead of the rig database (gt- prefix), causing "prefix mismatch: database uses 'hq' but you specified 'gt'" errors during polecat spawn. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
ea84079f8b
commit
598a39e708
@@ -33,6 +33,23 @@ func writeFakeBD(t *testing.T, script string) string {
|
||||
return binDir
|
||||
}
|
||||
|
||||
func assertBeadsDirLog(t *testing.T, logPath, want string) {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(logPath)
|
||||
if err != nil {
|
||||
t.Fatalf("reading beads dir log: %v", err)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
if len(lines) == 0 || (len(lines) == 1 && lines[0] == "") {
|
||||
t.Fatalf("expected beads dir log entries, got none")
|
||||
}
|
||||
for _, line := range lines {
|
||||
if line != want {
|
||||
t.Fatalf("BEADS_DIR = %q, want %q", line, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createTestRig(t *testing.T, root, name string) {
|
||||
t.Helper()
|
||||
|
||||
@@ -63,7 +80,6 @@ func createTestRig(t *testing.T, root, name string) {
|
||||
}
|
||||
|
||||
func TestDiscoverRigs(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
|
||||
// Create test rig
|
||||
@@ -102,7 +118,6 @@ func TestDiscoverRigs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetRig(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
|
||||
createTestRig(t, root, "test-rig")
|
||||
@@ -123,7 +138,6 @@ func TestGetRig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetRigNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
manager := NewManager(root, rigsConfig, git.NewGit(root))
|
||||
|
||||
@@ -134,7 +148,6 @@ func TestGetRigNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRigExists(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
rigsConfig.Rigs["exists"] = config.RigEntry{}
|
||||
|
||||
@@ -149,7 +162,6 @@ func TestRigExists(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemoveRig(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
rigsConfig.Rigs["to-remove"] = config.RigEntry{}
|
||||
|
||||
@@ -165,7 +177,6 @@ func TestRemoveRig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemoveRigNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
manager := NewManager(root, rigsConfig, git.NewGit(root))
|
||||
|
||||
@@ -176,7 +187,6 @@ func TestRemoveRigNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddRig_RejectsInvalidNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
manager := NewManager(root, rigsConfig, git.NewGit(root))
|
||||
|
||||
@@ -208,7 +218,6 @@ func TestAddRig_RejectsInvalidNames(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestListRigNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
rigsConfig.Rigs["rig1"] = config.RigEntry{}
|
||||
rigsConfig.Rigs["rig2"] = config.RigEntry{}
|
||||
@@ -222,7 +231,6 @@ func TestListRigNames(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRigSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
rig := &Rig{
|
||||
Name: "test",
|
||||
Polecats: []string{"a", "b", "c"},
|
||||
@@ -247,7 +255,6 @@ func TestRigSummary(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnsureGitignoreEntry_AddsEntry(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
manager := NewManager(root, rigsConfig, git.NewGit(root))
|
||||
|
||||
@@ -264,7 +271,6 @@ func TestEnsureGitignoreEntry_AddsEntry(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnsureGitignoreEntry_DoesNotDuplicate(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
manager := NewManager(root, rigsConfig, git.NewGit(root))
|
||||
|
||||
@@ -286,7 +292,6 @@ func TestEnsureGitignoreEntry_DoesNotDuplicate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnsureGitignoreEntry_AppendsToExisting(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, rigsConfig := setupTestTown(t)
|
||||
manager := NewManager(root, rigsConfig, git.NewGit(root))
|
||||
|
||||
@@ -392,12 +397,14 @@ exit 0
|
||||
}
|
||||
|
||||
func TestInitBeadsWritesConfigOnFailure(t *testing.T) {
|
||||
// Cannot use t.Parallel() due to t.Setenv
|
||||
rigPath := t.TempDir()
|
||||
beadsDir := filepath.Join(rigPath, ".beads")
|
||||
|
||||
script := `#!/usr/bin/env bash
|
||||
set -e
|
||||
if [[ -n "$BEADS_DIR_LOG" ]]; then
|
||||
echo "${BEADS_DIR:-<unset>}" >> "$BEADS_DIR_LOG"
|
||||
fi
|
||||
cmd="$1"
|
||||
shift
|
||||
if [[ "$cmd" == "init" ]]; then
|
||||
@@ -409,8 +416,9 @@ exit 1
|
||||
`
|
||||
|
||||
binDir := writeFakeBD(t, script)
|
||||
beadsDirLog := filepath.Join(t.TempDir(), "beads-dir.log")
|
||||
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
t.Setenv("EXPECT_BEADS_DIR", beadsDir)
|
||||
t.Setenv("BEADS_DIR_LOG", beadsDirLog)
|
||||
|
||||
manager := &Manager{}
|
||||
if err := manager.initBeads(rigPath, "gt"); err != nil {
|
||||
@@ -425,14 +433,14 @@ exit 1
|
||||
if string(config) != "prefix: gt\n" {
|
||||
t.Fatalf("config.yaml = %q, want %q", string(config), "prefix: gt\n")
|
||||
}
|
||||
assertBeadsDirLog(t, beadsDirLog, beadsDir)
|
||||
}
|
||||
|
||||
func TestInitAgentBeadsUsesRigBeadsDir(t *testing.T) {
|
||||
// Cannot use t.Parallel() due to t.Setenv
|
||||
// Rig-level agent beads (witness, refinery) are stored in rig beads.
|
||||
// Town-level agents (mayor, deacon) are created by gt install in town beads.
|
||||
// This test verifies that rig agent beads are created in the rig directory,
|
||||
// without an explicit BEADS_DIR override (uses cwd-based discovery).
|
||||
// using the resolved rig beads directory for BEADS_DIR.
|
||||
townRoot := t.TempDir()
|
||||
rigPath := filepath.Join(townRoot, "testrip")
|
||||
rigBeadsDir := filepath.Join(rigPath, ".beads")
|
||||
@@ -446,6 +454,9 @@ func TestInitAgentBeadsUsesRigBeadsDir(t *testing.T) {
|
||||
|
||||
script := `#!/usr/bin/env bash
|
||||
set -e
|
||||
if [[ -n "$BEADS_DIR_LOG" ]]; then
|
||||
echo "${BEADS_DIR:-<unset>}" >> "$BEADS_DIR_LOG"
|
||||
fi
|
||||
if [[ "$1" == "--no-daemon" ]]; then
|
||||
shift
|
||||
fi
|
||||
@@ -481,8 +492,10 @@ esac
|
||||
|
||||
binDir := writeFakeBD(t, script)
|
||||
agentLog := filepath.Join(t.TempDir(), "agents.log")
|
||||
beadsDirLog := filepath.Join(t.TempDir(), "beads-dir.log")
|
||||
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
t.Setenv("AGENT_LOG", agentLog)
|
||||
t.Setenv("BEADS_DIR_LOG", beadsDirLog)
|
||||
t.Setenv("BEADS_DIR", "") // Clear any existing BEADS_DIR
|
||||
|
||||
manager := &Manager{townRoot: townRoot}
|
||||
@@ -514,10 +527,10 @@ esac
|
||||
t.Errorf("expected agent %s was not created", id)
|
||||
}
|
||||
}
|
||||
assertBeadsDirLog(t, beadsDirLog, rigBeadsDir)
|
||||
}
|
||||
|
||||
func TestIsValidBeadsPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
prefix string
|
||||
want bool
|
||||
@@ -560,7 +573,6 @@ func TestIsValidBeadsPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInitBeadsRejectsInvalidPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
rigPath := t.TempDir()
|
||||
manager := &Manager{}
|
||||
|
||||
@@ -586,7 +598,6 @@ func TestInitBeadsRejectsInvalidPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeriveBeadsPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
want string
|
||||
@@ -635,7 +646,6 @@ func TestDeriveBeadsPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSplitCompoundWord(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
word string
|
||||
want []string
|
||||
|
||||
Reference in New Issue
Block a user