Skip to content

tool_shadow_path Reports a Shadow It Never Tested For, So a Copy Behind BIN_DIR Reads as a PATH Problem #1644

Description

@ptr727

What Happens

tool_shadow_path (host-setup/linux/install-tools.sh:890-897) resolves a tool with type -P and
returns the result whenever it is absolute and is not $BIN_DIR/$name:

resolved=$(type -P "$name" 2>/dev/null || true)
[[ $resolved == /* && $resolved != "$BIN_DIR/$name" ]] && printf '%s' "$resolved"

type -P answers with whatever PATH resolves to at that moment. When the managed copy at
$BIN_DIR/$name does not exist, PATH can only resolve to some other copy, so this returns one
unconditionally, whether or not that copy sits ahead of $BIN_DIR. The function's name says shadow
and both callers read it as one, but the ordering that would make it a shadow is never tested.

Two callers act on the result, and each states something the check did not establish:

  • tool_note, line 944. With $BIN_DIR/$tool absent it takes the else branch and reports
    <path> is installed outside <BIN_DIR> and keeps answering once the managed copy is installed.
    That is a claim about how PATH will resolve after an install, and nothing measured it. Where the
    other copy sits behind $BIN_DIR, it is false.
  • tool_unshadow, line 1036. It reaches dpkg-query -S and warns
    <path> belongs to a distro package and stays, put <BIN_DIR> ahead of it on PATH instead, which
    is advice to fix a PATH order that is already correct on any host whose PATH puts /usr/local/bin
    before /usr/bin, the Debian and Ubuntu default. The comment directly above it, line 1034, says
    this case is "found only when PATH puts it ahead of $BIN_DIR" -- which is precisely what
    tool_shadow_path does not establish.

The Property That Triggers It

A tool in the jq | uv | git-restore-mtime arm where both hold:

  1. $BIN_DIR/<tool> does not exist yet, and
  2. another copy exists in a directory PATH places behind $BIN_DIR.

A distro package under /usr/bin is the ordinary case, because the default PATH on Debian and
Ubuntu puts /usr/local/bin first. uv under ~/.local/bin is the case where the same code path
produces a true statement, since that directory really does precede $BIN_DIR, and removing the
copy really is the fix. That is why this has not surfaced: the arm's three tools split across both
states, and only one of them is reported wrongly.

Repro

Constructed, with no managed copy installed, and using a placeholder tool name so nothing on the
host is touched:

BIN_DIR=/usr/local/bin          # as install-tools.sh sets it, line 15
mkdir -p /tmp/behind
printf '#!/bin/sh\necho behind\n' > /tmp/behind/widget && chmod +x /tmp/behind/widget

# $BIN_DIR holds no widget, and /tmp/behind sits *after* $BIN_DIR on PATH.
PATH="$BIN_DIR:/tmp/behind:$PATH" type -P widget
# -> /tmp/behind/widget

# Absolute, and != $BIN_DIR/widget, so tool_shadow_path returns it and the caller reports that it
# "keeps answering once the managed copy is installed". It would not: the moment $BIN_DIR/widget
# exists, $BIN_DIR precedes /tmp/behind and the managed copy answers.

The same two lines run with PATH="/tmp/behind:$BIN_DIR:$PATH" are the genuine shadow, and
tool_shadow_path cannot tell the two apart.

Suggested Fix

Have tool_shadow_path establish the ordering it names rather than inferring it from a resolution
that cannot distinguish the two states: walk PATH and return a copy only where its directory
precedes $BIN_DIR. That splits the conflated cases into the three the callers actually need.

  • A copy ahead of $BIN_DIR: a real shadow. tool_note and tool_unshadow keep today's behavior.
  • A copy behind $BIN_DIR, with no managed copy: not a shadow. Worth no note at all, since
    installing the managed copy is itself the resolution.
  • A copy behind $BIN_DIR, with a managed copy present: already nothing, and stays nothing.

Worth checking install-tools.ps1 for the same conflation before settling the shape, since the two
sides are meant to agree on what a shadow is.

No activity

Activity on this issue will appear here.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpre-existingReview finding classed pre-existing per local-strict-review Disposing of FindingsscriptA defect in hub tooling

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions