diff --git a/CHANGELOG.md b/CHANGELOG.md index e04993c2dd..f8be3a4f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## v1.4.477 (2026-09-03) + +### PR [#2211](https://github.com/danielmiessler/Fabric/pull/2211) by [ksylvan](https://github.com/ksylvan): fix: prevent pattern loader temporary directory leaks + +- Prevent pattern loader leaks by lazily creating temporary directories during database population and cleaning them up after successful or failed downloads. +- Add regression tests for lazy directory creation and cleanup. + +## v1.4.476 (2026-09-03) + +### PR [#2210](https://github.com/danielmiessler/Fabric/pull/2210) by [ksylvan](https://github.com/ksylvan): feat: add Pzero as an OpenAI-compatible AI provider + +- Added Pzero as an OpenAI-compatible AI provider. +- Registered Pzero with its OpenAI-compatible API base URL. +- Added Pzero to the README’s list of supported AI providers. + ## v1.4.475 (2026-09-03) ### PR [#2209](https://github.com/danielmiessler/Fabric/pull/2209) by [kadiryildiz283](https://github.com/kadiryildiz283): feat(i18n): add Turkish (tr) translation diff --git a/README.md b/README.md index 117431c9db..e272973f20 100644 --- a/README.md +++ b/README.md @@ -374,6 +374,7 @@ Fabric supports a wide range of AI providers: - Mistral - Novita AI - OpenRouter +- Pzero - SiliconCloud - Synthorai - Together diff --git a/cmd/fabric/version.go b/cmd/fabric/version.go index 92ca8f9b0f..2d883c1ffb 100644 --- a/cmd/fabric/version.go +++ b/cmd/fabric/version.go @@ -1,3 +1,3 @@ package main -var version = "v1.4.475" +var version = "v1.4.477" diff --git a/cmd/generate_changelog/changelog.db b/cmd/generate_changelog/changelog.db index 744da0d4c1..fd6ae8a44b 100644 Binary files a/cmd/generate_changelog/changelog.db and b/cmd/generate_changelog/changelog.db differ diff --git a/internal/plugins/ai/openai_compatible/providers_config.go b/internal/plugins/ai/openai_compatible/providers_config.go index d9cfafceb3..13c659a555 100644 --- a/internal/plugins/ai/openai_compatible/providers_config.go +++ b/internal/plugins/ai/openai_compatible/providers_config.go @@ -289,6 +289,11 @@ var ProviderMap = map[string]ProviderConfig{ BaseURL: "https://openrouter.ai/api/v1", ImplementsResponses: false, }, + "Pzero": { + Name: "Pzero", + BaseURL: "https://api.pzero.studio/v1", + ImplementsResponses: false, + }, "SiliconCloud": { Name: "SiliconCloud", BaseURL: "https://api.siliconflow.cn/v1", diff --git a/internal/tools/patterns_loader.go b/internal/tools/patterns_loader.go index 8351a2d824..103356bfc3 100644 --- a/internal/tools/patterns_loader.go +++ b/internal/tools/patterns_loader.go @@ -59,13 +59,6 @@ type PatternsLoader struct { func (o *PatternsLoader) configure() (err error) { o.pathPatternsPrefix = fmt.Sprintf("%v/", o.DefaultFolder.Value) - // Use a consistent temp folder name regardless of the source path structure - tempDir, err := os.MkdirTemp("", "fabric-patterns-") - if err != nil { - return fmt.Errorf(i18n.T("patterns_failed_create_temp_folder"), err) - } - o.tempPatternsFolder = tempDir - return } @@ -96,6 +89,15 @@ func (o *PatternsLoader) PopulateDB() (err error) { fmt.Println() fmt.Println() + // Create the temp folder here, not in configure(), so invocations that + // do not download patterns do not leak an empty directory (issue #2190). + var tempDir string + if tempDir, err = os.MkdirTemp("", "fabric-patterns-"); err != nil { + return fmt.Errorf(i18n.T("patterns_failed_create_temp_folder"), err) + } + o.tempPatternsFolder = tempDir + defer os.RemoveAll(tempDir) + originalPath := o.DefaultFolder.Value if err = o.gitCloneAndCopy(); err != nil { return fmt.Errorf(i18n.T("patterns_failed_download_from_git"), err) diff --git a/internal/tools/patterns_loader_test.go b/internal/tools/patterns_loader_test.go new file mode 100644 index 0000000000..eb62e45b58 --- /dev/null +++ b/internal/tools/patterns_loader_test.go @@ -0,0 +1,63 @@ +package tools + +import ( + "os" + "path/filepath" + "testing" + + "github.com/danielmiessler/fabric/internal/plugins/db/fsdb" +) + +// Configure runs on every fabric invocation via the plugin registry. It must +// not create the patterns temp directory; only PopulateDB uses it. +func TestConfigureDoesNotCreateTempDir(t *testing.T) { + tmp := t.TempDir() + t.Setenv("TMPDIR", tmp) + t.Setenv("TMP", tmp) + t.Setenv("TEMP", tmp) + + loader := NewPatternsLoader(&fsdb.PatternsEntity{ + StorageEntity: &fsdb.StorageEntity{Dir: t.TempDir()}, + }) + if err := loader.Configure(); err != nil { + t.Fatalf("Configure() failed: %v", err) + } + + matches, err := filepath.Glob(filepath.Join(tmp, "fabric-patterns-*")) + if err != nil { + t.Fatal(err) + } + if len(matches) != 0 { + t.Errorf("Configure() created temp directories: %v", matches) + } +} + +// PopulateDB must create the temp directory lazily and remove it when done, +// even on failure. +func TestPopulateDBCleansUpTempDir(t *testing.T) { + tmp := t.TempDir() + t.Setenv("TMPDIR", tmp) + t.Setenv("TMP", tmp) + t.Setenv("TEMP", tmp) + + loader := NewPatternsLoader(&fsdb.PatternsEntity{ + StorageEntity: &fsdb.StorageEntity{Dir: t.TempDir()}, + }) + if err := loader.Configure(); err != nil { + t.Fatalf("Configure() failed: %v", err) + } + // Point at an invalid repo so PopulateDB fails fast without network. + loader.DefaultGitRepoUrl.Value = filepath.Join(t.TempDir(), "no-such-repo") + + if err := loader.PopulateDB(); err == nil { + t.Fatal("PopulateDB() unexpectedly succeeded with invalid repo") + } + + entries, err := os.ReadDir(tmp) + if err != nil { + t.Fatal(err) + } + for _, e := range entries { + t.Errorf("PopulateDB() left temp entry behind: %v", e.Name()) + } +} diff --git a/nix/pkgs/fabric/version.nix b/nix/pkgs/fabric/version.nix index a94204779e..60fc23d64d 100644 --- a/nix/pkgs/fabric/version.nix +++ b/nix/pkgs/fabric/version.nix @@ -1 +1 @@ -"1.4.475" +"1.4.477"