refactor(config): remove BEADS_DIR from agent environment and add doctor check (#455)

* 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>
This commit is contained in:
Julian Knutsen
2026-01-14 06:13:57 +00:00
committed by GitHub
parent 3cf77b2e8b
commit e7ca4908dc
19 changed files with 252 additions and 126 deletions

View File

@@ -841,13 +841,16 @@ func (d *Daemon) restartPolecatSession(rigName, polecatName, sessionName string)
return fmt.Errorf("cannot restart polecat: %s", reason)
}
// Calculate rig path for agent config resolution
rigPath := filepath.Join(d.config.TownRoot, rigName)
// Determine working directory (handle both new and old structures)
// New structure: polecats/<name>/<rigname>/
// Old structure: polecats/<name>/
workDir := filepath.Join(d.config.TownRoot, rigName, "polecats", polecatName, rigName)
workDir := filepath.Join(rigPath, "polecats", polecatName, rigName)
if _, err := os.Stat(workDir); os.IsNotExist(err) {
// Fall back to old structure
workDir = filepath.Join(d.config.TownRoot, rigName, "polecats", polecatName)
workDir = filepath.Join(rigPath, "polecats", polecatName)
}
// Verify the worktree exists
@@ -865,13 +868,11 @@ func (d *Daemon) restartPolecatSession(rigName, polecatName, sessionName string)
}
// Set environment variables using centralized AgentEnv
rigPath := filepath.Join(d.config.TownRoot, rigName)
envVars := config.AgentEnv(config.AgentEnvConfig{
Role: "polecat",
Rig: rigName,
AgentName: polecatName,
TownRoot: d.config.TownRoot,
BeadsDir: beads.ResolveBeadsDir(rigPath),
BeadsNoDaemon: true,
})

View File

@@ -483,21 +483,32 @@ func (d *Daemon) getStartCommand(roleConfig *beads.RoleConfig, parsed *ParsedIde
// Polecats and crew need environment variables set in the command
if parsed.RoleType == "polecat" {
envVars := config.AgentEnvSimple("polecat", parsed.RigName, parsed.AgentName)
// Add GT_ROOT and session ID env if available
envVars["GT_ROOT"] = d.config.TownRoot
if runtimeConfig.Session != nil && runtimeConfig.Session.SessionIDEnv != "" {
envVars["GT_SESSION_ID_ENV"] = runtimeConfig.Session.SessionIDEnv
var sessionIDEnv string
if runtimeConfig.Session != nil {
sessionIDEnv = runtimeConfig.Session.SessionIDEnv
}
envVars := config.AgentEnv(config.AgentEnvConfig{
Role: "polecat",
Rig: parsed.RigName,
AgentName: parsed.AgentName,
TownRoot: d.config.TownRoot,
SessionIDEnv: sessionIDEnv,
})
return config.PrependEnv("exec "+runtimeConfig.BuildCommand(), envVars)
}
if parsed.RoleType == "crew" {
envVars := config.AgentEnvSimple("crew", parsed.RigName, parsed.AgentName)
envVars["GT_ROOT"] = d.config.TownRoot
if runtimeConfig.Session != nil && runtimeConfig.Session.SessionIDEnv != "" {
envVars["GT_SESSION_ID_ENV"] = runtimeConfig.Session.SessionIDEnv
var sessionIDEnv string
if runtimeConfig.Session != nil {
sessionIDEnv = runtimeConfig.Session.SessionIDEnv
}
envVars := config.AgentEnv(config.AgentEnvConfig{
Role: "crew",
Rig: parsed.RigName,
AgentName: parsed.AgentName,
TownRoot: d.config.TownRoot,
SessionIDEnv: sessionIDEnv,
})
return config.PrependEnv("exec "+runtimeConfig.BuildCommand(), envVars)
}
@@ -507,21 +518,12 @@ func (d *Daemon) getStartCommand(roleConfig *beads.RoleConfig, parsed *ParsedIde
// setSessionEnvironment sets environment variables for the tmux session.
// Uses centralized AgentEnv for consistency, plus role bead custom env vars if available.
func (d *Daemon) setSessionEnvironment(sessionName string, roleConfig *beads.RoleConfig, parsed *ParsedIdentity) {
// Determine beads dir based on role type
var beadsPath string
if parsed.RigName != "" {
beadsPath = filepath.Join(d.config.TownRoot, parsed.RigName)
} else {
beadsPath = d.config.TownRoot
}
// Use centralized AgentEnv for base environment variables
envVars := config.AgentEnv(config.AgentEnvConfig{
Role: parsed.RoleType,
Rig: parsed.RigName,
AgentName: parsed.AgentName,
TownRoot: d.config.TownRoot,
BeadsDir: beads.ResolveBeadsDir(beadsPath),
})
for k, v := range envVars {
_ = d.tmux.SetEnvironment(sessionName, k, v)