Skip to content

Fixes for linux and yabridge#6

Open
scottmudge wants to merge 1 commit into
twardoch:mainfrom
scottmudge:linux-fixes
Open

Fixes for linux and yabridge#6
scottmudge wants to merge 1 commit into
twardoch:mainfrom
scottmudge:linux-fixes

Conversation

@scottmudge

@scottmudge scottmudge commented Feb 10, 2026

Copy link
Copy Markdown

This PR includes some fixes for Linux (not setting the correct cache path in ~/.config/...), and to allow scanning recursively into the ~/.vst3/ and allow scanning/loading yabridge plugins, which utilize a directory named ".vst3" rather than a distinct file.

It seems to work as expected. Scanning correctly finds the yabridge plugins, and yabridge is correctly invoked by pedalboard during scanning.

Tested and working with Python 3.14.2.

I also renamed the local types.py file, as it was shadowing the built-in types module, causing collisions/issues.

Summary by Sourcery

Improve plugin discovery and cache handling across platforms, with better support for Linux and yabridge-based VST3 plugins.

New Features:

  • Add recursive VST3 folder scanning to discover plugins located in nested directories, including yabridge-managed plugins.
  • Introduce a dedicated main() entry point function for the CLI module.

Bug Fixes:

  • Correct the cache directory location on Linux to use the XDG-style ~/.config/ path and ensure the cache folder exists before use.
  • Avoid plugin duplicates during VST3 scanning and skip Windows symlink targets used by yabridge.
  • Fix module shadowing issues by renaming the local types module and updating imports accordingly.

Enhancements:

  • Differentiate between VST3 bundles and files when scanning to better support both native and yabridge plugin layouts.

Build:

  • Declare rich and click as explicit runtime dependencies in the project configuration.

@sourcery-ai

sourcery-ai Bot commented Feb 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates plugin scanning and cache handling for better Linux and yabridge support, adds a proper CLI entry point, and renames an internal types module to avoid conflicts with Python’s built‑in types.

Sequence diagram for updated VST3 and yabridge scanning

sequenceDiagram
    participant ScannerIsolated
    participant FileSystem

    ScannerIsolated->>ScannerIsolated: _find_plugins_to_scan(extra_folders)
    ScannerIsolated->>ScannerIsolated: processed_stems = empty set
    ScannerIsolated->>ScannerIsolated: _get_vst3_folders(extra_folders)
    ScannerIsolated->>FileSystem: rglob("*.vst3") on each VST3 folder
    loop for each plugin_path
        ScannerIsolated->>ScannerIsolated: check plugin_path.stem in processed_stems
        alt stem already processed
            ScannerIsolated->>ScannerIsolated: optionally log Skipping duplicate
        else stem not processed
            ScannerIsolated->>ScannerIsolated: check "x86_64-win" in plugin_path.parts
            alt path contains x86_64-win
                ScannerIsolated->>ScannerIsolated: optionally log Skipping Windows symlink
            else not a Windows symlink
                ScannerIsolated->>ScannerIsolated: check f"vst3/{plugin_path.stem}" in ignores
                alt ignored
                    ScannerIsolated->>ScannerIsolated: continue loop
                else not ignored
                    ScannerIsolated->>FileSystem: plugin_path.is_dir()
                    alt plugin_path is directory
                        ScannerIsolated->>ScannerIsolated: add (path, stem, vst3) to plugin_tasks
                        ScannerIsolated->>ScannerIsolated: add stem to processed_stems
                        ScannerIsolated->>ScannerIsolated: optionally log Found VST3 bundle
                    else plugin_path is file
                        ScannerIsolated->>FileSystem: plugin_path.is_file()
                        alt plugin_path is file
                            ScannerIsolated->>ScannerIsolated: add (path, stem, vst3) to plugin_tasks
                            ScannerIsolated->>ScannerIsolated: add stem to processed_stems
                            ScannerIsolated->>ScannerIsolated: optionally log Found VST3 file
                        end
                    end
                end
            end
        end
    end
    ScannerIsolated-->>ScannerIsolated: return plugin_tasks
Loading

Sequence diagram for CLI entry point using main function

sequenceDiagram
    actor User
    participant Shell
    participant PythonInterpreter
    participant PedalboardMain as pedalboard_pluginary___main__
    participant Cli as cli_function

    User->>Shell: run pedalboard_pluginary command
    Shell->>PythonInterpreter: invoke console script entry point
    PythonInterpreter->>PedalboardMain: import pedalboard_pluginary.__main__
    PythonInterpreter->>PedalboardMain: if __name__ == __main__
    PedalboardMain->>PedalboardMain: main()
    PedalboardMain->>Cli: cli()
    Cli-->>User: execute CLI logic and output
Loading

File-Level Changes

Change Details Files
Extend VST3 plugin discovery to recurse into directories, support .vst3 bundles (including yabridge), and avoid duplicates and Windows symlink targets.
  • Replace shallow glob with recursive rglob when searching for .vst3 plugins.
  • Track processed plugin stems to avoid adding duplicate scan tasks for the same plugin name.
  • Skip plugin paths that contain the x86_64-win component to avoid yabridge Windows symlink targets.
  • Respect existing ignore rules when deciding whether to enqueue a plugin for scanning.
  • Differentiate between .vst3 directories (bundles) and files, logging each when verbose is enabled and adding appropriate scan tasks.
src/pedalboard_pluginary/scanner_isolated.py
Fix cache path resolution per-OS and ensure cache directories are created on demand.
  • Derive the current platform from sys.platform in addition to os.name for more precise OS detection.
  • Use APPDATA for Windows, ~/.config/APP_NAME for Linux/posix, and ~/Library/Application Support/APP_NAME for macOS.
  • Raise an explicit error for unsupported OS values.
  • Create the cache directory with os.makedirs(exist_ok=True) if it does not already exist before returning the cache path.
src/pedalboard_pluginary/data.py
Provide a callable main() entry point around the CLI and advertise CLI dependencies.
  • Wrap the existing cli() invocation in a main() function and call main() from the main guard.
  • Declare rich and click as explicit runtime dependencies in pyproject.toml to support the CLI’s output and argument handling.
src/pedalboard_pluginary/__main__.py
pyproject.toml
Rename the internal types module to avoid clashing with Python’s built-in types module.
  • Update imports to use type_definitions.ParameterValue instead of the local types module.
  • Aligns with the project’s refactor away from a file named types.py that shadowed the stdlib types module.
src/pedalboard_pluginary/models.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Fix Linux cache paths and add yabridge VST3 plugin support

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Fixed Linux cache path to use ~/.config/ instead of incorrect location
• Added recursive scanning for VST3 plugins to support yabridge directory structure
• Renamed types.py to type_definitions.py to avoid shadowing built-in module
• Improved plugin deduplication and Windows symlink filtering during scanning
• Added cache folder creation and better OS detection logic
• Added dependencies for rich and click libraries
Diagram
flowchart LR
  A["Linux Cache Path Fix"] --> B["Use ~/.config/ directory"]
  C["Yabridge Support"] --> D["Recursive VST3 scanning"]
  D --> E["Handle .vst3 directories"]
  E --> F["Skip Windows symlinks"]
  G["Module Shadowing Fix"] --> H["Rename types.py to type_definitions.py"]
  I["Plugin Deduplication"] --> J["Track processed stems"]
  K["OS Detection"] --> L["Improve platform checks"]
  L --> M["Create cache folders"]
Loading

Grey Divider

File Changes

1. src/pedalboard_pluginary/__main__.py ✨ Enhancement +4/-1

Refactor main entry point into function

• Extracted CLI invocation into a main() function
• Maintains backward compatibility with if __name__ == "__main__" guard

src/pedalboard_pluginary/main.py


2. src/pedalboard_pluginary/data.py 🐞 Bug fix +10/-2

Fix cache path and OS detection logic

• Fixed Linux cache path to use ~/.config/APP_NAME instead of incorrect location
• Improved OS detection using both os.name and sys.platform checks
• Added explicit handling for Windows, Linux, and macOS platforms
• Added automatic cache folder creation with os.makedirs()
• Added error handling for unsupported operating systems

src/pedalboard_pluginary/data.py


3. src/pedalboard_pluginary/models.py 🐞 Bug fix +1/-1

Update import from types to type_definitions

• Updated import to use type_definitions module instead of types
• Resolves module shadowing issue with built-in types module

src/pedalboard_pluginary/models.py


View more (4)
4. src/pedalboard_pluginary/scanner_isolated.py ✨ Enhancement +30/-5

Add recursive scanning and yabridge plugin support

• Changed VST3 scanning from glob() to rglob() for recursive directory traversal
• Added processed_stems set to track and skip duplicate plugin names
• Added logic to skip Windows symlinks in yabridge directories (x86_64-win)
• Added support for both .vst3 directories (bundles) and .vst3 files
• Added verbose debug logging for plugin discovery and filtering

src/pedalboard_pluginary/scanner_isolated.py


5. src/pedalboard_pluginary/serialization.py 🐞 Bug fix +1/-1

Update import from types to type_definitions

• Updated import to use type_definitions module instead of types
• Maintains all serialization functionality with corrected module reference

src/pedalboard_pluginary/serialization.py


6. pyproject.toml Dependencies +2/-0

Add rich and click dependencies

• Added rich library as a dependency for enhanced console output
• Added click library as a dependency for CLI improvements

pyproject.toml


7. src/pedalboard_pluginary/type_definitions.py Additional files +0/-0

...

src/pedalboard_pluginary/type_definitions.py


Grey Divider

Qodo Logo

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • The new get_cache_path OS detection mixes os.name and sys.platform with some unreachable branches (e.g. os.name == 'mac') and raises a ValueError with the os module instead of os.name/system_os; consider simplifying to a sys.platform-based check and fixing the error message to reference the actual platform string.
  • Using processed_stems to deduplicate VST3 plugins by plugin_path.stem may unintentionally drop distinct plugins that share the same name in different directories; consider including more of the path (e.g. parent directory) in the deduplication key if that scenario is possible in real setups.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `get_cache_path` OS detection mixes `os.name` and `sys.platform` with some unreachable branches (e.g. `os.name == 'mac'`) and raises a `ValueError` with the `os` module instead of `os.name`/`system_os`; consider simplifying to a `sys.platform`-based check and fixing the error message to reference the actual platform string.
- Using `processed_stems` to deduplicate VST3 plugins by `plugin_path.stem` may unintentionally drop distinct plugins that share the same name in different directories; consider including more of the path (e.g. parent directory) in the deduplication key if that scenario is possible in real setups.

## Individual Comments

### Comment 1
<location> `src/pedalboard_pluginary/scanner_isolated.py:299-305` </location>
<code_context>
     ) -> set[tuple[str, str, str]]:
         """Find all VST3 and AU plugins and return a set of (path, name, type)."""
         plugin_tasks = set()
+        processed_stems = set()  # Track plugin names to avoid duplicates

-        # VST3
+        # VST3 - use rglob to recursively find plugins (supports yabridge)
         for folder in self._get_vst3_folders(extra_folders):
-            for plugin_path in folder.glob("*.vst3"):
-                if f"vst3/{plugin_path.stem}" not in self.ignores:
-                    # For VST3, we often have multiple plugins in one file.
-                    # We will treat the file path as the initial task.
+            for plugin_path in folder.rglob("*.vst3"):
+                # Skip if we've already processed a plugin with this name
+                if plugin_path.stem in processed_stems:
+                    if self.verbose:
+                        logger.debug(f"Skipping duplicate: {plugin_path}")
</code_context>

<issue_to_address>
**issue:** Deduplicating solely by stem can hide distinct plugins that share the same filename in different folders.

This will skip any later plugin sharing the same basename, even when it’s a different plugin in another folder. If that behavior isn’t intentional, consider including the parent directory in the key (e.g. `(plugin_path.parent, plugin_path.stem)`) or limiting this deduplication to known yabridge layout patterns instead of applying it globally.
</issue_to_address>

### Comment 2
<location> `src/pedalboard_pluginary/data.py:17-22` </location>
<code_context>
 def get_cache_path(cache_name):
     """Get the path to a cache file."""
-    if os.name == "nt":
+    system_os = sys.platform
+    if os.name == "nt" or system_os == "win32":
         cache_folder = Path(os.getenv("APPDATA")) / APP_NAME
-    else:
+    elif os.name == "posix" or system_os == "linux":
+        cache_folder = Path.home() / ".config" / APP_NAME
+    elif os.name == "mac" or system_os == "darwin":
         cache_folder = Path.home() / "Library" / "Application Support" / APP_NAME
+    else:
</code_context>

<issue_to_address>
**suggestion:** OS detection is redundant and partially incorrect (os.name never equals 'mac').

`os.name` and `sys.platform` are being mixed in a way that’s both redundant and partly incorrect: `os.name` is `'posix'` for both Linux and macOS, so `os.name == "mac"` can never match. Since you already rely on `sys.platform`, consider using only that (e.g. `startswith("linux")`, `== "darwin"`, `startswith("win")`) and removing the `os.name` checks to keep the logic simpler and less error-prone.
</issue_to_address>

### Comment 3
<location> `src/pedalboard_pluginary/data.py:18-25` </location>
<code_context>
+    system_os = sys.platform
+    if os.name == "nt" or system_os == "win32":
         cache_folder = Path(os.getenv("APPDATA")) / APP_NAME
-    else:
+    elif os.name == "posix" or system_os == "linux":
+        cache_folder = Path.home() / ".config" / APP_NAME
+    elif os.name == "mac" or system_os == "darwin":
         cache_folder = Path.home() / "Library" / "Application Support" / APP_NAME
+    else:
+        raise ValueError(f"Unsupported OS: {os}")
+    if not os.path.exists(cache_folder):
+        os.makedirs(cache_folder, exist_ok=True)
</code_context>

<issue_to_address>
**issue (bug_risk):** The ValueError message references the os module instead of the actual OS identifier.

Here `os` refers to the imported module, so the exception will show something like `<module 'os' ...>` rather than the actual platform. Use `os.name`, `system_os`, or both in the message so it clearly reports the unsupported OS value.
</issue_to_address>

### Comment 4
<location> `src/pedalboard_pluginary/data.py:26-27` </location>
<code_context>
         cache_folder = Path.home() / "Library" / "Application Support" / APP_NAME
+    else:
+        raise ValueError(f"Unsupported OS: {os}")
+    if not os.path.exists(cache_folder):
+        os.makedirs(cache_folder, exist_ok=True)
     # Don't append .json anymore - let the caller specify the full filename
     return cache_folder / cache_name
</code_context>

<issue_to_address>
**suggestion:** Mixing pathlib Paths with os.path/os.makedirs is a bit inconsistent and can be simplified.

Since `cache_folder` is already a `Path`, you can replace the `os.path.exists`/`os.makedirs` block with `cache_folder.mkdir(parents=True, exist_ok=True)`. This keeps everything in the pathlib API and removes the small race window between checking existence and creating the directory.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +299 to +305
processed_stems = set() # Track plugin names to avoid duplicates

# VST3
# VST3 - use rglob to recursively find plugins (supports yabridge)
for folder in self._get_vst3_folders(extra_folders):
for plugin_path in folder.glob("*.vst3"):
if f"vst3/{plugin_path.stem}" not in self.ignores:
# For VST3, we often have multiple plugins in one file.
# We will treat the file path as the initial task.
for plugin_path in folder.rglob("*.vst3"):
# Skip if we've already processed a plugin with this name
if plugin_path.stem in processed_stems:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Deduplicating solely by stem can hide distinct plugins that share the same filename in different folders.

This will skip any later plugin sharing the same basename, even when it’s a different plugin in another folder. If that behavior isn’t intentional, consider including the parent directory in the key (e.g. (plugin_path.parent, plugin_path.stem)) or limiting this deduplication to known yabridge layout patterns instead of applying it globally.

Comment on lines +17 to +22
system_os = sys.platform
if os.name == "nt" or system_os == "win32":
cache_folder = Path(os.getenv("APPDATA")) / APP_NAME
else:
elif os.name == "posix" or system_os == "linux":
cache_folder = Path.home() / ".config" / APP_NAME
elif os.name == "mac" or system_os == "darwin":

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: OS detection is redundant and partially incorrect (os.name never equals 'mac').

os.name and sys.platform are being mixed in a way that’s both redundant and partly incorrect: os.name is 'posix' for both Linux and macOS, so os.name == "mac" can never match. Since you already rely on sys.platform, consider using only that (e.g. startswith("linux"), == "darwin", startswith("win")) and removing the os.name checks to keep the logic simpler and less error-prone.

Comment on lines +18 to +25
if os.name == "nt" or system_os == "win32":
cache_folder = Path(os.getenv("APPDATA")) / APP_NAME
else:
elif os.name == "posix" or system_os == "linux":
cache_folder = Path.home() / ".config" / APP_NAME
elif os.name == "mac" or system_os == "darwin":
cache_folder = Path.home() / "Library" / "Application Support" / APP_NAME
else:
raise ValueError(f"Unsupported OS: {os}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The ValueError message references the os module instead of the actual OS identifier.

Here os refers to the imported module, so the exception will show something like <module 'os' ...> rather than the actual platform. Use os.name, system_os, or both in the message so it clearly reports the unsupported OS value.

Comment on lines +26 to +27
if not os.path.exists(cache_folder):
os.makedirs(cache_folder, exist_ok=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Mixing pathlib Paths with os.path/os.makedirs is a bit inconsistent and can be simplified.

Since cache_folder is already a Path, you can replace the os.path.exists/os.makedirs block with cache_folder.mkdir(parents=True, exist_ok=True). This keeps everything in the pathlib API and removes the small race window between checking existence and creating the directory.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. get_cache_path() OS detection wrong 📘 Rule violation ⛯ Reliability
Description
get_cache_path() treats any os.name == 'posix' system as Linux, which will incorrectly route
macOS to the Linux cache path. It also raises ValueError(f"Unsupported OS: {os}"), which provides
misleading context and may expose internal module details.
Code

src/pedalboard_pluginary/data.py[R17-27]

+    system_os = sys.platform
+    if os.name == "nt" or system_os == "win32":
        cache_folder = Path(os.getenv("APPDATA")) / APP_NAME
-    else:
+    elif os.name == "posix" or system_os == "linux":
+        cache_folder = Path.home() / ".config" / APP_NAME
+    elif os.name == "mac" or system_os == "darwin":
        cache_folder = Path.home() / "Library" / "Application Support" / APP_NAME
+    else:
+        raise ValueError(f"Unsupported OS: {os}")
+    if not os.path.exists(cache_folder):
+        os.makedirs(cache_folder, exist_ok=True)
Evidence
Compliance IDs 3 and 10 require robust edge-case handling and validating assumptions. The current OS
branching will misclassify macOS (also os.name == 'posix'), and the unsupported-OS exception
message uses {os} (the module) rather than os.name/sys.platform, making failures harder to
diagnose correctly.

Rule 3: Generic: Robust Error Handling and Edge Case Management
CLAUDE.md, AGENTS.md
src/pedalboard_pluginary/data.py[17-27]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`get_cache_path()` OS detection is incorrect for macOS because it matches `os.name == &quot;posix&quot;` in the Linux branch, and the unsupported-OS error message uses the `os` module object rather than OS identifiers.

## Issue Context
This PR aims to fix Linux cache paths; incorrect branching can regress macOS behavior and create confusing failures.

## Fix Focus Areas
- src/pedalboard_pluginary/data.py[15-29]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. CLI not uv run+Fire 📘 Rule violation ⛯ Reliability
Description
The modified CLI entrypoint uses a plain python3 shebang and click rather than the required `uv
run header template and fire + rich` pattern. This diverges from the standardized CLI entrypoint
requirements.
Code

↗ src/pedalboard_pluginary/main.py

        print(yaml_str)
Evidence
Compliance ID 16 requires Python CLI scripts to include the standard uv run header block and use
fire + rich. The CLI module shows a python3 shebang and imports/uses click (not fire).

CLAUDE.md, AGENTS.md
src/pedalboard_pluginary/main.py[1-9]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The CLI entrypoint does not follow the required standard (`uv run` header + `fire` + `rich`).

## Issue Context
This module acts as a CLI entrypoint (`python -m pedalboard_pluginary`). If it is considered a CLI script under project standards, it should conform; otherwise, an explicit exemption/alternative standard should be established.

## Fix Focus Areas
- src/pedalboard_pluginary/__main__.py[1-40]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. VST3 dedupe may skip plugins 🐞 Bug ✓ Correctness
Description
VST3 discovery now deduplicates by plugin_path.stem, which can skip distinct plugins that share
the same filename in different folders. Since scanning/journaling/caching operates by full path,
those skipped paths may never be scanned or cached.
Code

src/pedalboard_pluginary/scanner_isolated.py[R299-331]

+        processed_stems = set()  # Track plugin names to avoid duplicates

-        # VST3
+        # VST3 - use rglob to recursively find plugins (supports yabridge)
        for folder in self._get_vst3_folders(extra_folders):
-            for plugin_path in folder.glob("*.vst3"):
-                if f"vst3/{plugin_path.stem}" not in self.ignores:
-                    # For VST3, we often have multiple plugins in one file.
-                    # We will treat the file path as the initial task.
+            for plugin_path in folder.rglob("*.vst3"):
+                # Skip if we've already processed a plugin with this name
+                if plugin_path.stem in processed_stems:
+                    if self.verbose:
+                        logger.debug(f"Skipping duplicate: {plugin_path}")
+                    continue
+                
+                # Skip files that are inside x86_64-win (these are Windows symlinks in yabridge)
+                if "x86_64-win" in plugin_path.parts:
+                    if self.verbose:
+                        logger.debug(f"Skipping Windows symlink: {plugin_path}")
+                    continue
+                
+                # Check ignores
+                if f"vst3/{plugin_path.stem}" in self.ignores:
+                    continue
+                
+                # If this is a .vst3 directory (yabridge or native bundle)
+                if plugin_path.is_dir():
+                    plugin_tasks.add((str(plugin_path), plugin_path.stem, "vst3"))
+                    processed_stems.add(plugin_path.stem)
+                    if self.verbose:
+                        logger.debug(f"Found VST3 bundle: {plugin_path}")
+                # Regular VST3 file (standalone)
+                elif plugin_path.is_file():
                    plugin_tasks.add((str(plugin_path), plugin_path.stem, "vst3"))
+                    processed_stems.add(plugin_path.stem)
+                    if self.verbose:
+                        logger.debug(f"Found VST3 file: {plugin_path}")
Evidence
Discovery filters duplicates purely by stem, but the scan workflow tracks and submits tasks by full
path and the SQLite cache schema enforces uniqueness by path (and even derives plugin_id from
plugin.path). That mismatch means the new stem-based skip can prevent certain paths from ever
reaching the scan/journal/cache pipeline.

src/pedalboard_pluginary/scanner_isolated.py[293-332]
src/pedalboard_pluginary/scanner_isolated.py[361-395]
src/pedalboard_pluginary/cache/sqlite_backend.py[47-52]
src/pedalboard_pluginary/cache/sqlite_backend.py[155-179]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
VST3 discovery deduplicates by `plugin_path.stem`, which can drop valid scan targets when two different plugin paths share a filename.

### Issue Context
The scan/journal/cache workflow uses full paths for task submission and cache uniqueness, so stem-based filtering can prevent some paths from ever being scanned.

### Fix Focus Areas
- src/pedalboard_pluginary/scanner_isolated.py[293-332]
- src/pedalboard_pluginary/scanner_isolated.py[361-395]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Recursive VST3 traversal cost 🐞 Bug ➹ Performance
Description
Using folder.rglob('*.vst3') will recursively walk the entire VST3 directory tree (including
descending into .vst3 bundles/directories), which can add significant filesystem overhead on large
plugin installations. This may slow startup/discovery noticeably.
Code

src/pedalboard_pluginary/scanner_isolated.py[R301-303]

+        # VST3 - use rglob to recursively find plugins (supports yabridge)
        for folder in self._get_vst3_folders(extra_folders):
-            for plugin_path in folder.glob("*.vst3"):
-                if f"vst3/{plugin_path.stem}" not in self.ignores:
-                    # For VST3, we often have multiple plugins in one file.
-                    # We will treat the file path as the initial task.
+            for plugin_path in folder.rglob("*.vst3"):
Evidence
The code explicitly switched from a shallow glob to rglob, which traverses all subdirectories
under each VST3 folder. Because VST3 bundles are commonly directories, recursion can end up walking
deep bundle contents even though only the top-level *.vst3 entries are typically needed as scan
tasks.

src/pedalboard_pluginary/scanner_isolated.py[301-303]
src/pedalboard_pluginary/scanner_isolated.py[463-485]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`Path.rglob(&#x27;*.vst3&#x27;)` recursively traverses all subdirectories under VST3 roots, which can be costly on systems with many plugins/bundles.

### Issue Context
You likely want recursive discovery for yabridge, but you can avoid walking inside `.vst3` bundles by pruning recursion once a `.vst3` directory is encountered.

### Fix Focus Areas
- src/pedalboard_pluginary/scanner_isolated.py[301-331]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +17 to +27
system_os = sys.platform
if os.name == "nt" or system_os == "win32":
cache_folder = Path(os.getenv("APPDATA")) / APP_NAME
else:
elif os.name == "posix" or system_os == "linux":
cache_folder = Path.home() / ".config" / APP_NAME
elif os.name == "mac" or system_os == "darwin":
cache_folder = Path.home() / "Library" / "Application Support" / APP_NAME
else:
raise ValueError(f"Unsupported OS: {os}")
if not os.path.exists(cache_folder):
os.makedirs(cache_folder, exist_ok=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. get_cache_path() os detection wrong 📘 Rule violation ⛯ Reliability

get_cache_path() treats any os.name == 'posix' system as Linux, which will incorrectly route
macOS to the Linux cache path. It also raises ValueError(f"Unsupported OS: {os}"), which provides
misleading context and may expose internal module details.
Agent Prompt
## Issue description
`get_cache_path()` OS detection is incorrect for macOS because it matches `os.name == "posix"` in the Linux branch, and the unsupported-OS error message uses the `os` module object rather than OS identifiers.

## Issue Context
This PR aims to fix Linux cache paths; incorrect branching can regress macOS behavior and create confusing failures.

## Fix Focus Areas
- src/pedalboard_pluginary/data.py[15-29]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant