Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions server/internal/daemon/execenv/codex_home.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ func prepareCodexHomeWithOpts(codexHome string, opts CodexHomeOptions, logger *s
return fmt.Errorf("create codex-home dir: %w", err)
}

// Remove stale Codex-generated memories from a prior task so the new
// task starts with a clean memory state. On the Prepare path the entire
// envRoot is already removed, so this is a harmless no-op; on the Reuse
// path (same agent+issue re-run) it prevents cross-task memory leakage
// (see #3130).
if err := os.RemoveAll(filepath.Join(codexHome, "memories")); err != nil {
logger.Warn("execenv: codex-home clean stale memories failed", "error", err)
}

// Symlink shared directories (sessions) so logs stay in the global home.
for _, name := range codexSymlinkedDirs {
src := filepath.Join(sharedHome, name)
Expand Down
60 changes: 60 additions & 0 deletions server/internal/daemon/execenv/codex_home_memories_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package execenv

import (
"log/slog"
"os"
"path/filepath"
"testing"
)

func TestPrepareCodexHome_CleansStaleMemories(t *testing.T) {
dir := t.TempDir()

codexHome := filepath.Join(dir, "codex-home")
sharedHome := filepath.Join(dir, "shared-codex")

// Seed a shared ~/.codex/ so prepareCodexHomeWithOpts doesn't warn.
os.MkdirAll(sharedHome, 0o755)
t.Setenv("CODEX_HOME", sharedHome)

// First call — simulate a prior task that left memories behind.
if err := os.MkdirAll(filepath.Join(codexHome, "memories"), 0o755); err != nil {
t.Fatalf("seed memories dir: %v", err)
}
staleMemory := filepath.Join(codexHome, "memories", "raw_memories.md")
if err := os.WriteFile(staleMemory, []byte("cwd: D:\\OldProject\nthread: old-thread-id\n"), 0o644); err != nil {
t.Fatalf("seed stale memory: %v", err)
}

// Verify the stale file exists before prepare.
if _, err := os.Stat(staleMemory); err != nil {
t.Fatalf("stale memory should exist before prepare: %v", err)
}

// Run prepare — should clean stale memories.
logger := slog.Default()
if err := prepareCodexHome(codexHome, logger); err != nil {
t.Fatalf("prepareCodexHome: %v", err)
}

// Stale memories directory should be gone.
if _, err := os.Stat(filepath.Join(codexHome, "memories")); !os.IsNotExist(err) {
t.Errorf("expected memories dir to be removed after prepare, err: %v", err)
}
}

func TestPrepareCodexHome_NoMemoriesDir_NoError(t *testing.T) {
dir := t.TempDir()

codexHome := filepath.Join(dir, "codex-home")
sharedHome := filepath.Join(dir, "shared-codex")

os.MkdirAll(sharedHome, 0o755)
t.Setenv("CODEX_HOME", sharedHome)

// No memories directory exists — should not error.
logger := slog.Default()
if err := prepareCodexHome(codexHome, logger); err != nil {
t.Fatalf("prepareCodexHome: %v", err)
}
}
Loading