put ~/.local/bin on the agent's PATH

A plugin may declare a dependency no package manager carries — tea, Gitea's
CLI, is the first — and installRelease puts it in ~/.local/bin: no root, and
nothing downloaded from the internet sitting beside what the distribution
installed.

But PM2 hands the platform a login-less PATH
(/usr/local/sbin:...:/snap/bin), so the agent it spawns inherited one too, and
the binary the platform had just installed was invisible to the agent it was
installed FOR.

Which is exactly what happened on the first real test: gitea's affordance told
the agent to use `tea`, the agent tried, and got `command not found` for
something sitting in the owner's home. The affordance worked perfectly and the
advice it gave was impossible to follow.

Appended rather than prepended, so a system binary of the same name still wins
— `identify` in os-deps is what settles which one is really wanted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 09:52:01 +00:00
co-authored by Claude Opus 5
parent 09f2cfa267
commit 79ced03bec
@@ -66,6 +66,23 @@ const NESTED_SESSION_ENV = [
function envWithoutParentSession(): Record<string, string> {
const out: Record<string, string> = { ...process.env } as Record<string, string>;
for (const name of NESTED_SESSION_ENV) delete out[name];
// `~/.local/bin` on PATH, because that is where the platform installs a plugin's release binaries.
//
// A plugin may declare a dependency no package manager carries — `tea`, Gitea's CLI, is the first —
// and `installRelease` puts it there: no root, and nothing downloaded from the internet sitting beside
// what the distribution installed. But PM2 hands this process a login-less PATH
// (`/usr/local/sbin:…:/snap/bin`), so the agent it spawns inherits one too, and the binary the platform
// just installed is invisible to the agent it was installed FOR.
//
// Which is exactly what happened: gitea's affordance told the agent to use `tea`, the agent tried, and
// got `command not found` for something sitting in the owner's home.
//
// Appended rather than prepended: a system binary of the same name should still win, and `identify` in
// os-deps is what settles which one is really wanted.
const localBin = join(homedir(), '.local', 'bin');
if (out.PATH && !out.PATH.split(':').includes(localBin)) out.PATH = `${out.PATH}:${localBin}`;
return out;
}