diff --git a/agent/codex/codex.go b/agent/codex/codex.go index d7cec6f40..7d7087cdb 100644 --- a/agent/codex/codex.go +++ b/agent/codex/codex.go @@ -339,6 +339,7 @@ func (a *Agent) StartSession(ctx context.Context, sessionID string) (core.AgentS codexHome := a.codexHome cliBin := a.cliBin cliExtraArgs := a.cliExtraArgs + workDir := a.workDir extraEnv := a.providerEnvLocked() extraEnv = append(extraEnv, a.sessionEnv...) var baseURL string @@ -361,13 +362,13 @@ func (a *Agent) StartSession(ctx context.Context, sessionID string) (core.AgentS } if backend == "app_server" { - return newAppServerSession(ctx, appServerURL, a.workDir, model, reasoningEffort, mode, sessionID, baseURL, provName, extraEnv, codexHome) + return newAppServerSession(ctx, appServerURL, workDir, model, reasoningEffort, mode, sessionID, baseURL, provName, extraEnv, codexHome) } if codexHome != "" { extraEnv = append(extraEnv, "CODEX_HOME="+codexHome) } - return newCodexSession(ctx, cliBin, cliExtraArgs, a.workDir, model, reasoningEffort, mode, sessionID, baseURL, extraEnv, provName) + return newCodexSession(ctx, cliBin, cliExtraArgs, workDir, model, reasoningEffort, mode, sessionID, baseURL, extraEnv, provName) } func (a *Agent) ListSessions(_ context.Context) ([]core.AgentSessionInfo, error) { @@ -438,11 +439,15 @@ func (a *Agent) WorkspaceAgentOptions() map[string]any { // ── SkillProvider implementation ────────────────────────────── func (a *Agent) SkillDirs() []string { - absDir, err := filepath.Abs(a.workDir) + a.mu.RLock() + workDir := a.workDir + codexHome := a.codexHome + a.mu.RUnlock() + absDir, err := filepath.Abs(workDir) if err != nil { - absDir = a.workDir + absDir = workDir } - return codexSkillDirs(absDir, a.codexHome) + return codexSkillDirs(absDir, codexHome) } // ── ContextCompressor implementation ────────────────────────── diff --git a/agent/codex/skilldirs_test.go b/agent/codex/skilldirs_test.go index 28c635f09..418adf608 100644 --- a/agent/codex/skilldirs_test.go +++ b/agent/codex/skilldirs_test.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "runtime" + "sync" "testing" ) @@ -88,3 +89,33 @@ func setTestHome(t *testing.T, home string) { t.Setenv("HOMEPATH", "") } } + +// TestSkillDirs_RaceFreeAgainstSetWorkDir pins the bug where SkillDirs +// read a.workDir and a.codexHome without holding a.mu, while +// SetWorkDir writes a.workDir under the lock. Run with -race to detect +// the data race; with the production fix the test stays clean. +func TestSkillDirs_RaceFreeAgainstSetWorkDir(t *testing.T) { + tmp := t.TempDir() + a := &Agent{workDir: tmp, codexHome: filepath.Join(tmp, "codex")} + + var wg sync.WaitGroup + for i := 0; i < 30; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + if i%2 == 0 { + a.SetWorkDir(filepath.Join(tmp, "a")) + } else { + a.SetWorkDir(filepath.Join(tmp, "b")) + } + }(i) + } + for i := 0; i < 30; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _ = a.SkillDirs() + }() + } + wg.Wait() +}