Skip to content

[Repo Assist] fix: scope addRootPath to document's workspace folder in multi-root workspaces#134

Draft
github-actions[bot] wants to merge 1 commit intomasterfrom
repo-assist/fix-addrootpath-multi-root-scope-ae8650ad8d6fb0f3
Draft

[Repo Assist] fix: scope addRootPath to document's workspace folder in multi-root workspaces#134
github-actions[bot] wants to merge 1 commit intomasterfrom
repo-assist/fix-addrootpath-multi-root-scope-ae8650ad8d6fb0f3

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot commented Apr 6, 2026

🤖 This is an automated PR from Repo Assist.

Problem

In a multi-root workspace where two or more folders each have phpcbf.executablePath set to a relative path (e.g. ./vendor/bin/phpcbf), the addRootPath() method was iterating over all workspace folders. If the same relative executable existed in multiple folders, the last folder in the iteration would win — regardless of which document was being formatted.

For example, formatting a file from Folder A could end up using Folder B's resolved phpcbf executable if both folders had ./vendor/bin/phpcbf.

This is a correctness bug in multi-root workspace setups with per-folder executablePath configuration.

Fix

  1. Pass configUri to addRootPath() — when formatting a document, its URI is now threaded through to addRootPath(). The method uses workspace.getWorkspaceFolder(configUri) to find exactly the right folder and only checks that folder's executable path.

  2. Replace fs.exists() with fs.existsSync() — the deprecated async fs.exists() callback was a separate race condition: loadSettings() could return before the callback fired, leaving executablePath as the unresolved relative path on the first format call after startup (see issue executablePath broken #36). fs.existsSync() resolves this synchronously.

The fallback (when no configUri is available, e.g. at activation time) is unchanged — it still iterates workspace folders.

Root cause

// Before: iterates ALL workspace folders — last match wins
addRootPath(prefix) {
    for (let wsFolder of workspace.workspaceFolders) {
        resources.push(wsFolder.uri);  // collects every folder
    }
    for (let resource of resources) {
        fs.exists(tmpExecutablePath, exists => {  // async race
            if (exists) this.executablePath = tmpExecutablePath;  // last write wins
        });
    }
}

// After: only checks the document's own workspace folder
addRootPath(prefix, configUri) {
    if (configUri) {
        resources = [configUri];  // use the formatting document's folder
    }
    for (let resource of resources) {
        if (fs.existsSync(tmpExecutablePath)) {  // synchronous, no race
            this.executablePath = tmpExecutablePath;
        }
    }
}

Test Status

Unit tests: 7/7 passing (npm run test:unit)

No integration test environment is available in this CI context (requires VS Code host). The change is backwards-compatible: single-root workspaces and the activation-time fallback are unaffected.

Related

Generated by Repo Assist ·

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@346204513ecfa08b81566450d7d599556807389f

…orkspaces

When a user has a multi-root workspace with per-folder phpcbf settings,
addRootPath() was iterating over ALL workspace folders to resolve a
relative executablePath. If multiple folders contained an executable at
the same relative path, the last one iterated would win — regardless of
which document was actually being formatted.

Fix: accept an optional configUri parameter and, when provided, resolve
the relative path against that document's workspace folder only. This
ensures folder A's document always uses folder A's resolved executable,
not folder B's.

Also replace the deprecated asynchronous fs.exists() callback with the
synchronous fs.existsSync(), eliminating the async race condition where
format() could be called before the callback had set executablePath.

Relates to: #36

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

executablePath broken

0 participants