From db0685a360c64fb78b7ecf08ded7240d3ccfdf28 Mon Sep 17 00:00:00 2001 From: esengine <359807859@qq.com> Date: Sun, 18 Oct 2026 04:06:50 -0700 Subject: [PATCH] fix(schedule): a project that is gone is not listed, so every path outside the list gets the same refusal --- internal/frontend/serve/schedules_test.go | 2 +- internal/runtime/scheduler/folders.go | 6 ++++ internal/runtime/scheduler/folders_test.go | 34 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/internal/frontend/serve/schedules_test.go b/internal/frontend/serve/schedules_test.go index d47c065b3c..51eb85a25f 100644 --- a/internal/frontend/serve/schedules_test.go +++ b/internal/frontend/serve/schedules_test.go @@ -129,7 +129,7 @@ func TestScheduleRefusalsCarryTheirOwnCodes(t *testing.T) { t.Fatal(err) } if status, raw := postSide(t, base+"/schedules/preview", map[string]any{"everySec": 7200, "workspace": gone, "prompt": "x"}); status != http.StatusBadRequest || !strings.Contains(string(raw), "schedule.workspace_unknown") { - t.Errorf("a trusted folder that was deleted = %d %s, want schedule.workspace_unknown (trust is filed under the folder's resolved path)", status, raw) + t.Errorf("a trusted folder that was deleted = %d %s, want schedule.workspace_unknown ", status, raw) } resp, err := http.Get(base + "/schedules/result?trigger=sch_x/1") if err != nil { diff --git a/internal/runtime/scheduler/folders.go b/internal/runtime/scheduler/folders.go index 5049dc19e2..0d765b79c8 100644 --- a/internal/runtime/scheduler/folders.go +++ b/internal/runtime/scheduler/folders.go @@ -1,6 +1,7 @@ package scheduler import ( + "os" "path/filepath" "runtime" "strings" @@ -30,6 +31,11 @@ func Folders(current string) []string { if trust, err := grants.Trust(p); err != nil || trust != config.WorkspaceTrusted || contains(out, p) { continue } + // A project that is no longer there is not one a schedule can read, and + // answering "gone" would tell a caller about paths it was never offered. + if info, err := os.Stat(p); err != nil || !info.IsDir() { + continue + } out = append(out, p) } return out diff --git a/internal/runtime/scheduler/folders_test.go b/internal/runtime/scheduler/folders_test.go index ab0a4b4f5d..ee3c414baf 100644 --- a/internal/runtime/scheduler/folders_test.go +++ b/internal/runtime/scheduler/folders_test.go @@ -4,6 +4,7 @@ import ( "errors" "os" "path/filepath" + "slices" "testing" "reasonix/internal/contract/config" @@ -86,3 +87,36 @@ func TestTheTrustedFoldersAreTheRememberedProjectsThePersonTrusted(t *testing.T) t.Fatalf("a project listed and current appears twice: %v", got) } } + +func TestATrustedProjectThatIsGoneOrIsNotAFolderIsNotListed(t *testing.T) { + t.Setenv("REASONIX_HOME", t.TempDir()) + gone, file := t.TempDir(), filepath.Join(t.TempDir(), "afile") + if err := os.WriteFile(file, []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + list := filepath.Join(config.MemoryUserDir(), workspacelist.FileName) + if err := workspacelist.Update(t.Context(), list, false, func(l *workspacelist.List) error { + l.Paths = append(l.Paths, gone, file) + return nil + }); err != nil { + t.Fatal(err) + } + grants := config.NewProjectGrantStore(config.RootsForHome("").Home()) + for _, p := range []string{gone, file} { + if err := grants.SetTrust(p, config.WorkspaceTrusted); err != nil { + t.Fatal(err) + } + } + if got := Folders(""); !slices.Contains(got, gone) { + t.Fatalf("a present trusted project is missing: %v", got) + } + if got := Folders(""); slices.Contains(got, file) { + t.Fatalf("a file was listed as a project: %v", got) + } + if err := os.RemoveAll(gone); err != nil { + t.Fatal(err) + } + if got := Folders(""); slices.Contains(got, gone) { + t.Fatalf("a deleted project is still listed: %v", got) + } +}