* fix(sling_test): update test for cook dir change
The cook command no longer needs database context and runs from cwd,
not the target rig directory. Update test to match this behavior
change from bd2a5ab5.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(tests): skip tests requiring missing binaries, handle --allow-stale
- Add skipIfAgentBinaryMissing helper to skip tests when codex/gemini
binaries aren't available in the test environment
- Update rig manager test stub to handle --allow-stale flag
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(config): remove BEADS_DIR from agent environment
Stop exporting BEADS_DIR in AgentEnv - agents should use beads redirect
mechanism instead of relying on environment variable. This prevents
prefix mismatches when agents operate across different beads databases.
Changes:
- Remove BeadsDir field from AgentEnvConfig
- Remove BEADS_DIR from env vars set on agent sessions
- Update doctor env_check to not expect BEADS_DIR
- Update all manager Start() calls to not pass BeadsDir
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(doctor): detect BEADS_DIR in tmux session environment
Add a doctor check that warns when BEADS_DIR is set in any Gas Town
tmux session. BEADS_DIR in the environment overrides prefix-based
routing and breaks multi-rig lookups - agents should use the beads
redirect mechanism instead.
The check:
- Iterates over all Gas Town tmux sessions (gt-* and hq-*)
- Checks if BEADS_DIR is set in the session environment
- Returns a warning with fix hint to restart sessions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: julianknutsen <julianknutsen@users.noreply.github>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
327 lines
7.8 KiB
Go
327 lines
7.8 KiB
Go
package config
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestAgentEnv_Mayor(t *testing.T) {
|
|
t.Parallel()
|
|
env := AgentEnv(AgentEnvConfig{
|
|
Role: "mayor",
|
|
TownRoot: "/town",
|
|
})
|
|
|
|
assertEnv(t, env, "GT_ROLE", "mayor")
|
|
assertEnv(t, env, "BD_ACTOR", "mayor")
|
|
assertEnv(t, env, "GIT_AUTHOR_NAME", "mayor")
|
|
assertEnv(t, env, "GT_ROOT", "/town")
|
|
assertNotSet(t, env, "GT_RIG")
|
|
assertNotSet(t, env, "BEADS_NO_DAEMON")
|
|
}
|
|
|
|
func TestAgentEnv_Witness(t *testing.T) {
|
|
t.Parallel()
|
|
env := AgentEnv(AgentEnvConfig{
|
|
Role: "witness",
|
|
Rig: "myrig",
|
|
TownRoot: "/town",
|
|
})
|
|
|
|
assertEnv(t, env, "GT_ROLE", "witness")
|
|
assertEnv(t, env, "GT_RIG", "myrig")
|
|
assertEnv(t, env, "BD_ACTOR", "myrig/witness")
|
|
assertEnv(t, env, "GIT_AUTHOR_NAME", "myrig/witness")
|
|
assertEnv(t, env, "GT_ROOT", "/town")
|
|
}
|
|
|
|
func TestAgentEnv_Polecat(t *testing.T) {
|
|
t.Parallel()
|
|
env := AgentEnv(AgentEnvConfig{
|
|
Role: "polecat",
|
|
Rig: "myrig",
|
|
AgentName: "Toast",
|
|
TownRoot: "/town",
|
|
BeadsNoDaemon: true,
|
|
})
|
|
|
|
assertEnv(t, env, "GT_ROLE", "polecat")
|
|
assertEnv(t, env, "GT_RIG", "myrig")
|
|
assertEnv(t, env, "GT_POLECAT", "Toast")
|
|
assertEnv(t, env, "BD_ACTOR", "myrig/polecats/Toast")
|
|
assertEnv(t, env, "GIT_AUTHOR_NAME", "Toast")
|
|
assertEnv(t, env, "BEADS_AGENT_NAME", "myrig/Toast")
|
|
assertEnv(t, env, "BEADS_NO_DAEMON", "1")
|
|
}
|
|
|
|
func TestAgentEnv_Crew(t *testing.T) {
|
|
t.Parallel()
|
|
env := AgentEnv(AgentEnvConfig{
|
|
Role: "crew",
|
|
Rig: "myrig",
|
|
AgentName: "emma",
|
|
TownRoot: "/town",
|
|
BeadsNoDaemon: true,
|
|
})
|
|
|
|
assertEnv(t, env, "GT_ROLE", "crew")
|
|
assertEnv(t, env, "GT_RIG", "myrig")
|
|
assertEnv(t, env, "GT_CREW", "emma")
|
|
assertEnv(t, env, "BD_ACTOR", "myrig/crew/emma")
|
|
assertEnv(t, env, "GIT_AUTHOR_NAME", "emma")
|
|
assertEnv(t, env, "BEADS_AGENT_NAME", "myrig/emma")
|
|
assertEnv(t, env, "BEADS_NO_DAEMON", "1")
|
|
}
|
|
|
|
func TestAgentEnv_Refinery(t *testing.T) {
|
|
t.Parallel()
|
|
env := AgentEnv(AgentEnvConfig{
|
|
Role: "refinery",
|
|
Rig: "myrig",
|
|
TownRoot: "/town",
|
|
BeadsNoDaemon: true,
|
|
})
|
|
|
|
assertEnv(t, env, "GT_ROLE", "refinery")
|
|
assertEnv(t, env, "GT_RIG", "myrig")
|
|
assertEnv(t, env, "BD_ACTOR", "myrig/refinery")
|
|
assertEnv(t, env, "GIT_AUTHOR_NAME", "myrig/refinery")
|
|
assertEnv(t, env, "BEADS_NO_DAEMON", "1")
|
|
}
|
|
|
|
func TestAgentEnv_Deacon(t *testing.T) {
|
|
t.Parallel()
|
|
env := AgentEnv(AgentEnvConfig{
|
|
Role: "deacon",
|
|
TownRoot: "/town",
|
|
})
|
|
|
|
assertEnv(t, env, "GT_ROLE", "deacon")
|
|
assertEnv(t, env, "BD_ACTOR", "deacon")
|
|
assertEnv(t, env, "GIT_AUTHOR_NAME", "deacon")
|
|
assertEnv(t, env, "GT_ROOT", "/town")
|
|
assertNotSet(t, env, "GT_RIG")
|
|
assertNotSet(t, env, "BEADS_NO_DAEMON")
|
|
}
|
|
|
|
func TestAgentEnv_Boot(t *testing.T) {
|
|
t.Parallel()
|
|
env := AgentEnv(AgentEnvConfig{
|
|
Role: "boot",
|
|
TownRoot: "/town",
|
|
})
|
|
|
|
assertEnv(t, env, "GT_ROLE", "boot")
|
|
assertEnv(t, env, "BD_ACTOR", "deacon-boot")
|
|
assertEnv(t, env, "GIT_AUTHOR_NAME", "boot")
|
|
assertEnv(t, env, "GT_ROOT", "/town")
|
|
assertNotSet(t, env, "GT_RIG")
|
|
assertNotSet(t, env, "BEADS_NO_DAEMON")
|
|
}
|
|
|
|
func TestAgentEnv_WithRuntimeConfigDir(t *testing.T) {
|
|
t.Parallel()
|
|
env := AgentEnv(AgentEnvConfig{
|
|
Role: "polecat",
|
|
Rig: "myrig",
|
|
AgentName: "Toast",
|
|
TownRoot: "/town",
|
|
RuntimeConfigDir: "/home/user/.config/claude",
|
|
})
|
|
|
|
assertEnv(t, env, "CLAUDE_CONFIG_DIR", "/home/user/.config/claude")
|
|
}
|
|
|
|
func TestAgentEnv_WithoutRuntimeConfigDir(t *testing.T) {
|
|
t.Parallel()
|
|
env := AgentEnv(AgentEnvConfig{
|
|
Role: "polecat",
|
|
Rig: "myrig",
|
|
AgentName: "Toast",
|
|
TownRoot: "/town",
|
|
})
|
|
|
|
assertNotSet(t, env, "CLAUDE_CONFIG_DIR")
|
|
}
|
|
|
|
func TestAgentEnvSimple(t *testing.T) {
|
|
t.Parallel()
|
|
env := AgentEnvSimple("polecat", "myrig", "Toast")
|
|
|
|
assertEnv(t, env, "GT_ROLE", "polecat")
|
|
assertEnv(t, env, "GT_RIG", "myrig")
|
|
assertEnv(t, env, "GT_POLECAT", "Toast")
|
|
// Simple doesn't set TownRoot, so key should be absent
|
|
// (not empty string which would override tmux session environment)
|
|
assertNotSet(t, env, "GT_ROOT")
|
|
}
|
|
|
|
func TestAgentEnv_EmptyTownRootOmitted(t *testing.T) {
|
|
t.Parallel()
|
|
// Regression test: empty TownRoot should NOT create keys in the map.
|
|
// If it was set to empty string, ExportPrefix would generate "export GT_ROOT= ..."
|
|
// which overrides tmux session environment where it's correctly set.
|
|
env := AgentEnv(AgentEnvConfig{
|
|
Role: "polecat",
|
|
Rig: "myrig",
|
|
AgentName: "Toast",
|
|
TownRoot: "", // explicitly empty
|
|
})
|
|
|
|
// Key should be absent, not empty string
|
|
assertNotSet(t, env, "GT_ROOT")
|
|
|
|
// Other keys should still be set
|
|
assertEnv(t, env, "GT_ROLE", "polecat")
|
|
assertEnv(t, env, "GT_RIG", "myrig")
|
|
}
|
|
|
|
func TestExportPrefix(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
env map[string]string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "empty",
|
|
env: map[string]string{},
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "single var",
|
|
env: map[string]string{"FOO": "bar"},
|
|
expected: "export FOO=bar && ",
|
|
},
|
|
{
|
|
name: "multiple vars sorted",
|
|
env: map[string]string{
|
|
"ZZZ": "last",
|
|
"AAA": "first",
|
|
"MMM": "middle",
|
|
},
|
|
expected: "export AAA=first MMM=middle ZZZ=last && ",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ExportPrefix(tt.env)
|
|
if result != tt.expected {
|
|
t.Errorf("ExportPrefix() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildStartupCommandWithEnv(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
env map[string]string
|
|
agentCmd string
|
|
prompt string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "no env no prompt",
|
|
env: map[string]string{},
|
|
agentCmd: "claude",
|
|
prompt: "",
|
|
expected: "claude",
|
|
},
|
|
{
|
|
name: "env no prompt",
|
|
env: map[string]string{"GT_ROLE": "polecat"},
|
|
agentCmd: "claude",
|
|
prompt: "",
|
|
expected: "export GT_ROLE=polecat && claude",
|
|
},
|
|
{
|
|
name: "env with prompt",
|
|
env: map[string]string{"GT_ROLE": "polecat"},
|
|
agentCmd: "claude",
|
|
prompt: "gt prime",
|
|
expected: `export GT_ROLE=polecat && claude "gt prime"`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := BuildStartupCommandWithEnv(tt.env, tt.agentCmd, tt.prompt)
|
|
if result != tt.expected {
|
|
t.Errorf("BuildStartupCommandWithEnv() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeEnv(t *testing.T) {
|
|
t.Parallel()
|
|
a := map[string]string{"A": "1", "B": "2"}
|
|
b := map[string]string{"B": "override", "C": "3"}
|
|
|
|
result := MergeEnv(a, b)
|
|
|
|
assertEnv(t, result, "A", "1")
|
|
assertEnv(t, result, "B", "override")
|
|
assertEnv(t, result, "C", "3")
|
|
}
|
|
|
|
func TestFilterEnv(t *testing.T) {
|
|
t.Parallel()
|
|
env := map[string]string{"A": "1", "B": "2", "C": "3"}
|
|
|
|
result := FilterEnv(env, "A", "C")
|
|
|
|
assertEnv(t, result, "A", "1")
|
|
assertNotSet(t, result, "B")
|
|
assertEnv(t, result, "C", "3")
|
|
}
|
|
|
|
func TestWithoutEnv(t *testing.T) {
|
|
t.Parallel()
|
|
env := map[string]string{"A": "1", "B": "2", "C": "3"}
|
|
|
|
result := WithoutEnv(env, "B")
|
|
|
|
assertEnv(t, result, "A", "1")
|
|
assertNotSet(t, result, "B")
|
|
assertEnv(t, result, "C", "3")
|
|
}
|
|
|
|
func TestEnvToSlice(t *testing.T) {
|
|
t.Parallel()
|
|
env := map[string]string{"A": "1", "B": "2"}
|
|
|
|
result := EnvToSlice(env)
|
|
|
|
if len(result) != 2 {
|
|
t.Errorf("EnvToSlice() returned %d items, want 2", len(result))
|
|
}
|
|
|
|
// Check both entries exist (order not guaranteed)
|
|
found := make(map[string]bool)
|
|
for _, s := range result {
|
|
found[s] = true
|
|
}
|
|
if !found["A=1"] || !found["B=2"] {
|
|
t.Errorf("EnvToSlice() = %v, want [A=1, B=2]", result)
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func assertEnv(t *testing.T, env map[string]string, key, expected string) {
|
|
t.Helper()
|
|
if got := env[key]; got != expected {
|
|
t.Errorf("env[%q] = %q, want %q", key, got, expected)
|
|
}
|
|
}
|
|
|
|
func assertNotSet(t *testing.T, env map[string]string, key string) {
|
|
t.Helper()
|
|
if _, ok := env[key]; ok {
|
|
t.Errorf("env[%q] should not be set, but is %q", key, env[key])
|
|
}
|
|
}
|