Skip to content

bug(launcher): --package-root warning is wrong when a path declaration plus a different requested root forces the take-over #1487

Description

@dev-addous

What

In an isolated home whose settings.json declares gentle-pi as a path package, passing --package-root <different-dir> still forces the take-over and injects that requested root — while bin/gentle-shell.mjs prints a warning saying the flag is ignored.

Why it happens

The clause that looks like it decides is not the one that decides. decideTakeOver short-circuits on packageRootExplicit, but that boolean is only forwarded as true in --link mode:

// bin/gentle-shell.mjs:1193
takeOver = decideTakeOver({
  declaration,
  realPackageRoot: realEffectivePackageRoot,          // ← realpath of the REQUESTED root
  realDeclaredDir,
  packageRootExplicit: home.mode === "link" && packageRootExplicit,
});

The requested root reaches that call through effectivePackageRoot:

// bin/gentle-shell.mjs:1145
const effectivePackageRoot = packageRootExplicit ? resolvePath(args.packageRoot) : packageRoot;
// bin/gentle-shell.mjs:1191
const realEffectivePackageRoot = safeRealpath(effectivePackageRoot);

and the path clause compares the declared dir against it:

// runtime/gentle-shell-launcher.mjs:635
export function decideTakeOver(input) {
  if (input.packageRootExplicit) return true;
  if (input.declaration === undefined) return false;
  if (input.declaration.kind === "npm") return false;
  const realDeclaredDir = input.realDeclaredDir ?? input.declaration.dir;
  return realDeclaredDir !== input.realPackageRoot;   // ← declared dir vs REQUESTED root
}

On take-over the injected root is the requested one (bin:1226 passes packageRoot: effectivePackageRoot to buildPiInvocation), so the flag genuinely took effect.

Meanwhile the warning fires on a condition that is only about the flag being present, the mode not being --link, and a declaration existing — evaluated before the decision:

// bin/gentle-shell.mjs:1186
if (packageRootExplicit && home.mode !== "link" && declaration !== undefined) {
  process.stderr.write(`gentle-shell: --package-root only forces a take-over in --link mode; ${home.dir} declares gentle-pi, so the installed package is used and ${args.packageRoot} is ignored\n`);
}

The comment above it states the same thing and is wrong the same way: "in every other mode a declared home silently keeps using its declared gentle-pi and --package-root has no effect at all". That is true for an npm: declaration, and for a path: declaration only while the requested root resolves to the declared one.

Reproduction

buildPiInvocation and decideTakeOver imported from the real launcher and driven with these inputs (isolated home, home.mode !== "link", packageRootExplicit forwarded as home.mode === "link" && args.packageRoot !== undefined):

# declaration --package-root takeOver injected args
1 npm:gentle-pi@3.5.1 R false []
2 path: = launcher's own root R (different) true ["--no-extensions","-e",R]
3 path: = launcher's own root none false []
4 path: = another dir none true ["--no-extensions","-e",<own root>]
5 no declaration R false ["-e",R]
6 --link, path: = launcher's own root R (different) true ["--no-extensions","-e",R]

Row 2 is the defect: the flag forced the take-over and its root was injected, while stderr said it was ignored. Row 5 shows the comment's "no effect at all" is only true of declared homes — without a declaration the flag still selects which root is loaded.

The decision columns are identical on the installed gentle-pi@3.7.0 and on main at fd050add; the two differ only in the asset arguments, which is expected (the 3.7.0 release predates 0031c09).

Expected

One of:

  1. Documentation-only: the warning and the code comment should describe the actual rule — the flag is ignored outright for an npm: declaration, and for a path: declaration only while the requested root resolves to the declared package root. Row 2 then needs no warning at all.
  2. Behaviour change: forward packageRootExplicit in every mode so the flag's effect matches what the warning already claims. This changes what an isolated home with a path declaration does when the flag is passed, so it needs a maintainer decision rather than a drive-by.

Option 1 leaves the code as the source of truth; option 2 makes the warning honest by changing behaviour. Either way the current combination is inconsistent.

Coverage gap

tests/gentle-shell-bin.test.ts covers an npm: declaration (:1864, which returns false before the path clause) and a run with no declaration (:2311). Isolated + path: declaration + --package-root has no test, which is why the mismatch survives.

Provenance

Code read at fd050add; bin/gentle-shell.mjs was last changed 2026-09-22 (5b9d6a7b), before the v3.7.0 tag, and the launcher behaviour described here is present in the published 3.7.0 package. Found while correcting a documentation sentence in #1486.

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 working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions