mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
cli: add package path from install context (#26189)
## Why Codex package installs include helper binaries in `codex-path`, such as the bundled `rg`. Package-layout launches should add that directory before user commands run, but standalone launches were missing it while npm launches only worked because `codex.js` had its own legacy `PATH` rewrite. That made npm and standalone package behavior diverge. Shell snapshot restoration can also reset `PATH` after runtime setup. Any package-owned `PATH` prepend has to be recorded as an explicit runtime override so shells, unified exec, and user-shell commands keep access to `codex-path` after a snapshot is sourced. ## Repro Before this change, a curl-installed package could contain `rg` under `codex-path` but still fail to put it on `PATH`: ```shell mkdir /tmp/test-codex-curl curl -fsSL https://chatgpt.com/codex/install.sh \ | CODEX_HOME=/tmp/test-codex-curl CODEX_NON_INTERACTIVE=1 sh /tmp/test-codex-curl/packages/standalone/current/bin/codex exec \ --skip-git-repo-check 'print `which -a rg`' find /tmp/test-codex-curl -name rg ``` The `which -a rg` output omitted the packaged helper even though `find` showed it under `/tmp/test-codex-curl/packages/standalone/releases/.../codex-path/rg`. The npm install path behaved differently only because `codex-cli/bin/codex.js` had legacy `PATH` rewriting: ```shell mkdir /tmp/test-codex-npm cd /tmp/test-codex-npm npm install @openai/codex ./node_modules/.bin/codex exec --skip-git-repo-check 'print `which -a rg`' ``` That printed the npm package's `vendor/<target>/codex-path/rg` first. This PR moves that behavior into Rust-side package launch setup so curl/standalone and npm/bun launches agree without JS rewriting `PATH`. ## What Changed - `codex-rs/arg0` now uses `InstallContext::current().package_layout.path_dir` to prepend the package helper directory before any threads are created. - Package helper `PATH` setup is independent from the temporary arg0 alias setup, so `codex-path` is still added even if CODEX_HOME tempdir, lock, or symlink setup fails. - `codex-rs/install-context` detects the canonical package layout we ship: `bin/`, `codex-resources/`, and `codex-path/` next to `codex-package.json`. - Shell, local unified exec, and user-shell runtimes now record package `codex-path` prepends in `explicit_env_overrides`, matching the existing zsh-fork behavior so shell snapshots cannot restore over the package helper path. - Remote unified exec requests do not receive the local app-server package path overlay. - `codex-cli/bin/codex.js` no longer computes or overrides `PATH`; it only locates the native binary in the canonical package layout and passes npm/bun management metadata. - Added regression tests for `PATH` ordering, package layout detection, and shell snapshot preservation of package path prepends. ## Verification - `node --check codex-cli/bin/codex.js` - `just test -p codex-install-context -p codex-arg0` - `just test -p codex-core user_shell_snapshot_preserves_package_path_prepend` - `just test -p codex-core tools::runtimes::tests` - `just bazel-lock-update` - `just bazel-lock-check` - `just fix -p codex-install-context -p codex-arg0 -p codex-core`
This commit is contained in:
committed by
GitHub
Unverified
parent
80b65e9945
commit
6bcccb0ee6
+21
-55
@@ -75,45 +75,25 @@ if (!platformPackage) {
|
||||
throw new Error(`Unsupported target triple: ${targetTriple}`);
|
||||
}
|
||||
|
||||
const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
|
||||
const localVendorRoot = path.join(__dirname, "..", "vendor");
|
||||
const packageBinaryPath = (vendorRoot) =>
|
||||
path.join(vendorRoot, targetTriple, "bin", codexBinaryName);
|
||||
const legacyBinaryPath = (vendorRoot) =>
|
||||
path.join(vendorRoot, targetTriple, "codex", codexBinaryName);
|
||||
|
||||
function resolveNativePackage(vendorRoot) {
|
||||
const packageRoot = path.join(vendorRoot, targetTriple);
|
||||
const binaryPath = packageBinaryPath(vendorRoot);
|
||||
if (existsSync(binaryPath)) {
|
||||
return {
|
||||
binaryPath,
|
||||
pathDir: path.join(packageRoot, "codex-path"),
|
||||
};
|
||||
function findCodexExecutable() {
|
||||
let vendorRoot;
|
||||
try {
|
||||
const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
|
||||
vendorRoot = path.join(path.dirname(packageJsonPath), "vendor");
|
||||
} catch {
|
||||
vendorRoot = path.join(__dirname, "..", "vendor");
|
||||
}
|
||||
|
||||
const legacyPath = legacyBinaryPath(vendorRoot);
|
||||
if (existsSync(legacyPath)) {
|
||||
return {
|
||||
binaryPath: legacyPath,
|
||||
pathDir: path.join(packageRoot, "path"),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
let nativePackage;
|
||||
try {
|
||||
const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
|
||||
nativePackage = resolveNativePackage(
|
||||
path.join(path.dirname(packageJsonPath), "vendor"),
|
||||
const codexExecutable = path.join(
|
||||
vendorRoot,
|
||||
targetTriple,
|
||||
"bin",
|
||||
process.platform === "win32" ? "codex.exe" : "codex",
|
||||
);
|
||||
} catch {
|
||||
nativePackage = resolveNativePackage(localVendorRoot);
|
||||
}
|
||||
if (existsSync(codexExecutable)) {
|
||||
return codexExecutable;
|
||||
}
|
||||
|
||||
if (!nativePackage) {
|
||||
const packageManager = detectPackageManager();
|
||||
const updateCommand =
|
||||
packageManager === "bun"
|
||||
@@ -124,7 +104,7 @@ if (!nativePackage) {
|
||||
);
|
||||
}
|
||||
|
||||
const { binaryPath, pathDir } = nativePackage;
|
||||
const binaryPath = findCodexExecutable();
|
||||
|
||||
// Use an asynchronous spawn instead of spawnSync so that Node is able to
|
||||
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
|
||||
@@ -132,16 +112,6 @@ const { binaryPath, pathDir } = nativePackage;
|
||||
// and guarantees that when either the child terminates or the parent
|
||||
// receives a fatal signal, both processes exit in a predictable manner.
|
||||
|
||||
function getUpdatedPath(newDirs) {
|
||||
const pathSep = process.platform === "win32" ? ";" : ":";
|
||||
const existingPath = process.env.PATH || "";
|
||||
const updatedPath = [
|
||||
...newDirs,
|
||||
...existingPath.split(pathSep).filter(Boolean),
|
||||
].join(pathSep);
|
||||
return updatedPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use heuristics to detect the package manager that was used to install Codex
|
||||
* in order to give the user a hint about how to update it.
|
||||
@@ -167,19 +137,15 @@ function detectPackageManager() {
|
||||
return userAgent ? "npm" : null;
|
||||
}
|
||||
|
||||
const additionalDirs = [];
|
||||
if (existsSync(pathDir)) {
|
||||
additionalDirs.push(pathDir);
|
||||
}
|
||||
const updatedPath = getUpdatedPath(additionalDirs);
|
||||
|
||||
const env = { ...process.env, PATH: updatedPath };
|
||||
const packageManagerEnvVar =
|
||||
detectPackageManager() === "bun"
|
||||
? "CODEX_MANAGED_BY_BUN"
|
||||
: "CODEX_MANAGED_BY_NPM";
|
||||
env[packageManagerEnvVar] = "1";
|
||||
env.CODEX_MANAGED_PACKAGE_ROOT = realpathSync(path.join(__dirname, ".."));
|
||||
const env = {
|
||||
...process.env,
|
||||
[packageManagerEnvVar]: "1",
|
||||
CODEX_MANAGED_PACKAGE_ROOT: realpathSync(path.join(__dirname, "..")),
|
||||
};
|
||||
|
||||
const child = spawn(binaryPath, process.argv.slice(2), {
|
||||
stdio: "inherit",
|
||||
|
||||
Reference in New Issue
Block a user