diff --git a/src/servers/sidecar/claude/claude-manager.ts b/src/servers/sidecar/claude/claude-manager.ts index a45ba4b3..f44cb25c 100644 --- a/src/servers/sidecar/claude/claude-manager.ts +++ b/src/servers/sidecar/claude/claude-manager.ts @@ -66,6 +66,23 @@ const NESTED_SESSION_ENV = [ function envWithoutParentSession(): Record { const out: Record = { ...process.env } as Record; 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; }