Skip to content

Fix PATH accumulation when switching between projects with .luca/tools#15

Merged
albertodebortoli merged 4 commits into
mainfrom
fix/path-accumulation-between-projects
Jun 12, 2026
Merged

Fix PATH accumulation when switching between projects with .luca/tools#15
albertodebortoli merged 4 commits into
mainfrom
fix/path-accumulation-between-projects

Conversation

@albertodebortoli

Copy link
Copy Markdown
Member

Problem

When navigating between two directories that both contain a .luca/tools folder, the PATH would accumulate entries for both projects. More critically, switching back to the first project would leave the second project's entry at a higher priority position in PATH, causing the wrong tool version to be resolved.

Reproduction steps:

  1. cd /path/to/project-Aproject-A/.luca/tools is added to PATH
  2. cd /path/to/project-Bproject-B/.luca/tools is added to PATH (now both are present)
  3. cd /path/to/project-Aproject-A/.luca/tools is already in PATH, so the function returns early. project-B/.luca/tools remains and is still first in PATH.

The result is that tools from project B are resolved instead of project A's tools.

Root Cause

In update_path(), the cleanup loop that removes stale .luca/tools entries was placed inside an else branch — it only ran when the current directory had no .luca/tools folder:

if [ -d "$tool_bin_dir" ]; then
    # add to PATH...
else
    # ← cleanup only ran here, never when both dirs have .luca/tools
    if [[ ":$PATH:" == *"/$TOOL_FOLDER/tools:"* ]]; then
        # strip stale entries...
    fi
fi

When navigating from project A to project B (both having .luca/tools), the if branch fires, skips cleanup entirely, and simply appends B's entry. The stale A entry is never removed until you navigate to a directory with no .luca/tools at all.

Fix

Move the cleanup loop unconditionally to the top of update_path(), before the add step. The function now always:

  1. Scans PATH for any */.luca/tools entries
  2. Keeps only those whose project root is an ancestor of (or equal to) $PWD
  3. Appends the current directory's tool_bin_dir if it exists and isn't already present
update_path() {
  local tool_bin_dir="$(pwd)/$TOOL_FOLDER/tools"
  local current_pwd="$(pwd)"

  # Always clean up stale entries first
  if [[ ":$PATH:" == *"/$TOOL_FOLDER/tools:"* ]]; then
    # ... strip any entry whose project root doesn't match $PWD ...
    export PATH="$new_path"
  fi

  # Then add current entry if needed
  if [ -d "$tool_bin_dir" ]; then
    case ":$PATH:" in
      *":$tool_bin_dir:"*) ;;
      *) export PATH="$tool_bin_dir:$PATH" ;;
    esac
  fi
}

Behaviour After Fix

Navigation PATH result
cd project-A project-A/.luca/tools:...
cd project-B project-B/.luca/tools:... (A's entry removed)
cd project-A project-A/.luca/tools:... (B's entry removed)
cd /no-luca-dir ... (all .luca/tools entries removed)
cd project-A/subdir project-A/.luca/tools:... (parent project entry preserved)

All existing idempotency and subdirectory-traversal behaviour is preserved.

The cleanup loop in update_path was nested inside the else branch, meaning
it only ran when the current directory had no .luca/tools. When navigating
from project A to project B (both with .luca/tools), B's entry was appended
to PATH while A's was never removed. Navigating back to A found its entry
already present and returned early, leaving B's stale entry ahead of A's.

Move the cleanup unconditionally to the top of the function so stale
.luca/tools entries are always evicted before the current directory's entry
is (re-)added. The add step is now a simple append-if-missing that follows
the cleaned PATH.
Cover the two missing cases: navigating from project A to project B
removes A's entry, and navigating back to A removes B's entry.
update_path previously only checked $(pwd)/.luca/tools, so navigating
directly into a project subdirectory from outside would never pick up
the project's tools. Walk up from $PWD to / and use the first ancestor
that contains .luca/tools.

Simplify the PATH cleanup to match against the single resolved
tool_bin_dir rather than checking project-root prefixes.

Add a test covering direct entry into a subdirectory without first
visiting the project root.
The upward search now stops before reaching \$HOME, so
\$HOME/.luca/tools (the global Luca installation present on every
user's machine) is never treated as a project-specific tools directory.
Projects nested inside HOME are still found correctly.
@albertodebortoli albertodebortoli marked this pull request as ready for review June 12, 2026 20:31
@albertodebortoli albertodebortoli merged commit c165f75 into main Jun 12, 2026
2 checks passed
@albertodebortoli albertodebortoli deleted the fix/path-accumulation-between-projects branch June 12, 2026 20:32
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