From 48ace2cbf36ce242e28d4b8f0ec297b5bcab310c Mon Sep 17 00:00:00 2001 From: joshuavial Date: Tue, 20 Jan 2026 01:33:19 +1300 Subject: [PATCH] fix(handoff): preserve GT_AGENT across session restarts (#788) Adds GT_AGENT env var to track agent override when using --agent flag. Handoff reads and preserves GT_AGENT so non-default agents persist across restarts. Co-authored-by: joshuavial --- internal/cmd/handoff.go | 20 +++++++++++- internal/config/loader.go | 4 +++ internal/config/loader_test.go | 60 ++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/internal/cmd/handoff.go b/internal/cmd/handoff.go index fc73ac4a..f5dd4574 100644 --- a/internal/cmd/handoff.go +++ b/internal/cmd/handoff.go @@ -384,7 +384,20 @@ func buildRestartCommand(sessionName string) (string, error) { // 3. export Claude-related env vars (not inherited by fresh shell) // 4. run claude with the startup beacon (triggers immediate context loading) // Use exec to ensure clean process replacement. - runtimeCmd := config.GetRuntimeCommandWithPrompt("", beacon) + // + // Check if current session is using a non-default agent (GT_AGENT env var). + // If so, preserve it across handoff by using the override variant. + currentAgent := os.Getenv("GT_AGENT") + var runtimeCmd string + if currentAgent != "" { + var err error + runtimeCmd, err = config.GetRuntimeCommandWithPromptAndAgentOverride("", beacon, currentAgent) + if err != nil { + return "", fmt.Errorf("resolving agent config: %w", err) + } + } else { + runtimeCmd = config.GetRuntimeCommandWithPrompt("", beacon) + } // Build environment exports - role vars first, then Claude vars var exports []string @@ -398,6 +411,11 @@ func buildRestartCommand(sessionName string) (string, error) { } } + // Preserve GT_AGENT across handoff so agent override persists + if currentAgent != "" { + exports = append(exports, "GT_AGENT="+currentAgent) + } + // Add Claude-related env vars from current environment for _, name := range claudeEnvVars { if val := os.Getenv(name); val != "" { diff --git a/internal/config/loader.go b/internal/config/loader.go index 8ba00360..9a859fb2 100644 --- a/internal/config/loader.go +++ b/internal/config/loader.go @@ -1353,6 +1353,10 @@ func BuildStartupCommandWithAgentOverride(envVars map[string]string, rigPath, pr if rc.Session != nil && rc.Session.SessionIDEnv != "" { resolvedEnv["GT_SESSION_ID_ENV"] = rc.Session.SessionIDEnv } + // Record agent override so handoff can preserve it + if agentOverride != "" { + resolvedEnv["GT_AGENT"] = agentOverride + } // Build environment export prefix var exports []string diff --git a/internal/config/loader_test.go b/internal/config/loader_test.go index a47d9c95..299eb687 100644 --- a/internal/config/loader_test.go +++ b/internal/config/loader_test.go @@ -2670,3 +2670,63 @@ func TestQuoteForShell(t *testing.T) { }) } } + +func TestBuildStartupCommandWithAgentOverride_SetsGTAgent(t *testing.T) { + t.Parallel() + townRoot := t.TempDir() + rigPath := filepath.Join(townRoot, "testrig") + + // Create necessary config files + townSettings := NewTownSettings() + if err := SaveTownSettings(TownSettingsPath(townRoot), townSettings); err != nil { + t.Fatalf("SaveTownSettings: %v", err) + } + if err := SaveRigSettings(RigSettingsPath(rigPath), NewRigSettings()); err != nil { + t.Fatalf("SaveRigSettings: %v", err) + } + + cmd, err := BuildStartupCommandWithAgentOverride( + map[string]string{"GT_ROLE": constants.RoleWitness}, + rigPath, + "", + "gemini", + ) + if err != nil { + t.Fatalf("BuildStartupCommandWithAgentOverride: %v", err) + } + + // Should include GT_AGENT=gemini in export so handoff can preserve it + if !strings.Contains(cmd, "GT_AGENT=gemini") { + t.Errorf("expected GT_AGENT=gemini in command, got: %q", cmd) + } +} + +func TestBuildStartupCommandWithAgentOverride_NoGTAgentWhenNoOverride(t *testing.T) { + t.Parallel() + townRoot := t.TempDir() + rigPath := filepath.Join(townRoot, "testrig") + + // Create necessary config files + townSettings := NewTownSettings() + if err := SaveTownSettings(TownSettingsPath(townRoot), townSettings); err != nil { + t.Fatalf("SaveTownSettings: %v", err) + } + if err := SaveRigSettings(RigSettingsPath(rigPath), NewRigSettings()); err != nil { + t.Fatalf("SaveRigSettings: %v", err) + } + + cmd, err := BuildStartupCommandWithAgentOverride( + map[string]string{"GT_ROLE": constants.RoleWitness}, + rigPath, + "", + "", // No override + ) + if err != nil { + t.Fatalf("BuildStartupCommandWithAgentOverride: %v", err) + } + + // Should NOT include GT_AGENT when no override is used + if strings.Contains(cmd, "GT_AGENT=") { + t.Errorf("expected no GT_AGENT in command when no override, got: %q", cmd) + } +}