diff --git a/server/internal/daemon/daemon.go b/server/internal/daemon/daemon.go index 68bd10209e..e57319d8a2 100644 --- a/server/internal/daemon/daemon.go +++ b/server/internal/daemon/daemon.go @@ -1461,7 +1461,7 @@ func (d *Daemon) handleTask(ctx context.Context, task Task, slot int) { // can look up the issue later. Written last so that a mid-task crash // leaves the directory as an orphan (cleaned up by GCOrphanTTL). if result.EnvRoot != "" { - if err := execenv.WriteGCMeta(result.EnvRoot, task.IssueID, task.WorkspaceID); err != nil { + if err := execenv.WriteGCMeta(result.EnvRoot, task.IssueID, task.WorkspaceID, taskLog); err != nil { taskLog.Warn("write gc meta failed (non-fatal)", "error", err) } } diff --git a/server/internal/daemon/execenv/execenv.go b/server/internal/daemon/execenv/execenv.go index 4fb0e36080..70454f3a01 100644 --- a/server/internal/daemon/execenv/execenv.go +++ b/server/internal/daemon/execenv/execenv.go @@ -53,7 +53,7 @@ type TaskContextForEnv struct { ProjectTitle string // human-readable project title ProjectResources []ProjectResourceForEnv // resources attached to the project ChatSessionID string // non-empty for chat tasks - AutopilotRunID string // non-empty for autopilot run_only tasks + AutopilotRunID string // non-empty for autopilot run_only tasks AutopilotID string AutopilotTitle string AutopilotDescription string @@ -214,7 +214,11 @@ type GCMeta struct { const gcMetaFile = ".gc_meta.json" // WriteGCMeta writes GC metadata into the given directory. -func WriteGCMeta(envRoot, issueID, workspaceID string) error { +func WriteGCMeta(envRoot, issueID, workspaceID string, logger *slog.Logger) error { + if issueID == "" { + logger.Warn("execenv: skipping .gc_meta.json write: issue_id is empty", "envRoot", envRoot, "workspaceID", workspaceID) + return nil + } if envRoot == "" { return nil } diff --git a/server/internal/daemon/execenv/execenv_test.go b/server/internal/daemon/execenv/execenv_test.go index b724fee1c9..e047db72a5 100644 --- a/server/internal/daemon/execenv/execenv_test.go +++ b/server/internal/daemon/execenv/execenv_test.go @@ -2,6 +2,7 @@ package execenv import ( "encoding/json" + "io" "log/slog" "os" "path/filepath" @@ -14,6 +15,10 @@ func testLogger() *slog.Logger { return slog.Default() } +func discardLogger() *slog.Logger { + return slog.New(slog.NewTextHandler(io.Discard, nil)) +} + func TestShortID(t *testing.T) { t.Parallel() tests := []struct { @@ -1916,7 +1921,7 @@ func TestWriteReadGCMeta(t *testing.T) { issueID := "a1b2c3d4-e5f6-7890-abcd-ef1234567890" wsID := "ws-test-001" - if err := WriteGCMeta(dir, issueID, wsID); err != nil { + if err := WriteGCMeta(dir, issueID, wsID, discardLogger()); err != nil { t.Fatalf("WriteGCMeta: %v", err) } @@ -1938,11 +1943,23 @@ func TestWriteReadGCMeta(t *testing.T) { func TestWriteGCMeta_EmptyRoot(t *testing.T) { t.Parallel() - if err := WriteGCMeta("", "issue", "ws"); err != nil { + if err := WriteGCMeta("", "issue", "ws", discardLogger()); err != nil { t.Fatalf("expected nil for empty root, got %v", err) } } +func TestWriteGCMeta_EmptyIssueID(t *testing.T) { + t.Parallel() + dir := t.TempDir() + + if err := WriteGCMeta(dir, "", "ws", discardLogger()); err != nil { + t.Fatalf("expected nil for empty issue ID, got %v", err) + } + if _, err := os.Stat(filepath.Join(dir, gcMetaFile)); !os.IsNotExist(err) { + t.Fatalf("expected gc meta file to be absent, got err=%v", err) + } +} + func TestReadGCMeta_NoFile(t *testing.T) { t.Parallel() dir := t.TempDir()