Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ CodexHost does it differently:

Please read the [contributing guide](CONTRIBUTING.md) before opening an issue or PR. See [repository maintenance automation](docs/operations/repository-maintenance.md) for PR title labels, CI summaries, and pre-release checks.

Requirements: the official Codex Desktop, Node.js 22.19+ or 24, and Rust.
Requirements: the official Codex Desktop, Node.js 22.19+ (22.x), 24 or 25, and Rust.

```bash
git clone https://github.com/BytePioneer-AI/codex-host
Expand Down
4 changes: 2 additions & 2 deletions crates/updater/src/ssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fn script(request: &Request) -> Result<String, Box<dyn Error>> {
// their recorded runtime ahead of that default, provided the executable still exists.
let node = r#"if [ -n "${CODEXHOST_HOST_NODE_PATH:-}" ] && [ -x "$CODEXHOST_HOST_NODE_PATH" ]; then PATH="$(dirname "$CODEXHOST_HOST_NODE_PATH"):$PATH"; export PATH; fi;
command -v node >/dev/null 2>&1 || exit 45;
node -e 'const [major, minor] = process.versions.node.split(".").map(Number); process.exit(major === 24 || (major === 22 && minor >= 19) ? 0 : 1)' >/dev/null 2>&1 || exit 55;
node -e 'const [major, minor] = process.versions.node.split(".").map(Number); process.exit(major === 24 || major === 25 || (major === 22 && minor >= 19) ? 0 : 1)' >/dev/null 2>&1 || exit 55;
"#;
// With a damaged installation SSH sessions fall through to stock Codex, which then owns the
// control socket. There is no managed service to stop in that case; starting replaces it.
Expand Down Expand Up @@ -220,7 +220,7 @@ pub fn apply() -> Result<(), Box<dyn Error>> {
Some(58) => "codexhost was not detected over SSH. Install it on the remote computer or add it to the login shell PATH, then retry",
Some(59) => "The active codexhost does not match the global npm installation. Uninstall only the remote service, or remove the package manually on the remote computer",
Some(60) => "The remote service was uninstalled, but removing the codexhost package failed. Check npm permissions on the remote computer and retry",
Some(55) => "Remote Node.js is unsupported. Select Node.js 22.19 or later in the 22.x series, or Node.js 24.x",
Some(55) => "Remote Node.js is unsupported. Select Node.js 22.19 or later in the 22.x series, or Node.js 24.x/25.x",
Some(46) => "npm installation failed. Check network access and global installation permissions",
Some(47) => "Remote service configuration failed. Check Codex CLI and the remote desktop login",
Some(50) => "Remote service could not stop. Check the remote service before retrying",
Expand Down
55 changes: 55 additions & 0 deletions crates/updater/src/ssh/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,61 @@ fn executable(path: &std::path::Path, source: &str) {
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700)).unwrap();
}

#[cfg(unix)]
#[test]
fn node_version_gate_accepts_25_and_preserves_supported_version_boundaries() {
let root = std::env::temp_dir().join(format!("codexhost-ssh-versions-{}", std::process::id()));
let mut r = request();
r.action = "install".into();
r.version = Some("1.2.3".into());
// Execute the generated install script with real JavaScript evaluation. Only the
// reported Node version and the package/service mutations are replaced by fixtures.
let mocks = r#"
node(){ command node -e 'Object.defineProperty(process.versions, "node", { value: process.env.TEST_NODE_VERSION }); eval(process.argv[1]);' "$2"; }
npm(){ printf '%s\n' "npm $*" >> "$HOME/calls"; }
codex(){ :; }
codexhost(){ printf '%s\n' "codexhost $*" >> "$HOME/calls"; }
"#;
for (version, supported) in [
("20.19.0", false),
("22.18.0", false),
("22.19.0", true),
("23.0.0", false),
("24.0.0", true),
("25.0.0", true),
("25.4.0", true),
("26.0.0", false),
] {
let home = root.join(version);
std::fs::create_dir_all(&home).unwrap();
let result = Command::new("/bin/sh")
.args(["-c", &format!("{mocks}{}", script(&r).unwrap())])
.env_clear()
.env("PATH", std::env::var_os("PATH").unwrap())
.env("HOME", &home)
.env("TEST_NODE_VERSION", version)
.output()
.unwrap();
assert_eq!(
result.status.code(),
Some(if supported { 0 } else { 55 }),
"{version}: {}",
String::from_utf8_lossy(&result.stderr)
);
if supported {
assert_eq!(response_state(&result.stdout).unwrap(), "installed");
assert_eq!(
std::fs::read_to_string(home.join("calls")).unwrap(),
"npm install -g @codexhost/cli@1.2.3\ncodexhost remote install\ncodexhost remote start\n"
);
} else {
assert!(!home.join("calls").exists(), "{version}");
}
assert!(!home.join(".codexhost/ssh-setup.lock").exists());
}
std::fs::remove_dir_all(root).unwrap();
}

#[cfg(unix)]
#[test]
fn missing_tools_report_specific_failures_before_installation_or_service_changes() {
Expand Down
2 changes: 1 addition & 1 deletion docs/platforms/remote/remote-ssh-host.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Update local codexhost to the latest version before installing the remote end be

Log in to the remote computer once through SSH from your local terminal and confirm its identity. The page uses your existing SSH key or alias and does not show a password prompt.

The remote computer needs Node.js, npm and Codex CLI, with permission for your user to install npm packages. Use Node.js 22.19 or later in the 22.x series, or Node.js 24.x. Installation loads your interactive login shell, including configured version managers such as nvm. Existing installations prefer the executable Node.js path recorded by codexhost; an unsupported recorded version must be updated before maintenance. On a remote Mac, keep the desktop user logged in when using Claude Code.
The remote computer needs Node.js, npm and Codex CLI, with permission for your user to install npm packages. Use Node.js 22.19 or later in the 22.x series, or Node.js 24.x/25.x. Installation loads your interactive login shell, including configured version managers such as nvm. Existing installations prefer the executable Node.js path recorded by codexhost; an unsupported recorded version must be updated before maintenance. On a remote Mac, keep the desktop user logged in when using Claude Code.

## Step 2: Add an SSH connection

Expand Down
2 changes: 1 addition & 1 deletion docs/platforms/remote/remote-ssh-host.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Web 控制台的「远程连接」(`#remote-connections`)也提供同一套

先在本机终端通过 SSH 登录一次远程电脑,并确认电脑身份。页面使用已有的 SSH 密钥或 SSH 别名,不会弹出密码输入框。

远程电脑需要已经安装 Node.js、npm 和 Codex CLI,并允许当前用户通过 npm 安装软件。Node.js 需为 22.19 及以上的 22.x 版本,或 24.x。安装时会加载用户的交互式登录 shell,包括已配置的 nvm 等版本管理器。已有安装优先使用 codexhost 记录且仍可执行的 Node 路径;如果该版本不受支持,需要先更新它再维护远程服务。如果远程电脑是 Mac,使用 Claude Code 时还需要保持该电脑的桌面用户已登录。
远程电脑需要已经安装 Node.js、npm 和 Codex CLI,并允许当前用户通过 npm 安装软件。Node.js 需为 22.19 及以上的 22.x 版本,或 24.x/25.x。安装时会加载用户的交互式登录 shell,包括已配置的 nvm 等版本管理器。已有安装优先使用 codexhost 记录且仍可执行的 Node 路径;如果该版本不受支持,需要先更新它再维护远程服务。如果远程电脑是 Mac,使用 Claude Code 时还需要保持该电脑的桌面用户已登录。

## 第二步:添加 SSH 连接

Expand Down
2 changes: 1 addition & 1 deletion docs/project/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ CodexHost는 다른 방식을 택합니다.

Issue나 PR을 제출하기 전에 [기여 안내](../../CONTRIBUTING.md)를 읽어 주세요. PR 제목 라벨, 간단한 CI 결과, 릴리스 전 검증은 [저장소 유지 관리 자동화](../operations/repository-maintenance.md)를 참고하세요.

환경 요구 사항: 공식 Codex Desktop, Node.js 22.19+ 또는 24, Rust.
환경 요구 사항: 공식 Codex Desktop, Node.js 22.19+ (22.x), 24 또는 25, Rust.

```bash
git clone https://github.com/BytePioneer-AI/codex-host
Expand Down
2 changes: 1 addition & 1 deletion docs/project/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ CodexHost 的做法不同:

提交 Issue 或 PR 前可阅读[贡献说明](../../CONTRIBUTING.md);PR 标题标签、简短 CI 结果和发布前校验见[仓库维护自动化](../operations/repository-maintenance.md)。

环境要求:官方 Codex Desktop、Node.js 22.19+ 或 24、Rust。
环境要求:官方 Codex Desktop、Node.js 22.19+(22.x)、24 或 25、Rust。

```bash
git clone https://github.com/BytePioneer-AI/codex-host
Expand Down
8 changes: 4 additions & 4 deletions openspec/specs/engineering-workspace/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@
## Requirements
### Requirement: 工具链兼容且依赖可复现

仓库 MUST 使用 Node.js 22.19+ 或 24、可用的 npm、Rust 工具链和已提交的依赖锁文件。开发和真实 Gate MUST 只校验 Node.js major version 为 22 或 24,MUST NOT 因 Node.js patch version 或 npm version 与仓库记录不同而阻止运行。CI 和发布流程 MAY 记录或固定已验证的精确工具链版本用于复现,但该记录不得成为本地事实探索的版本门禁。
仓库 MUST 使用 Node.js 22.19+(22.x)、24 或 25、可用的 npm、Rust 工具链和已提交的依赖锁文件。开发和真实 Gate MUST 只校验 Node.js major version 为 22、24 或 25,MUST NOT 因 Node.js patch version 或 npm version 与仓库记录不同而阻止运行。CI 和发布流程 MAY 记录或固定已验证的精确工具链版本用于复现,但该记录不得成为本地事实探索的版本门禁。

#### Scenario: 全新检出安装依赖

- **WHEN** 开发者使用 Node.js 22.19+ 或 24 和可用 npm 在全新检出中安装依赖
- **WHEN** 开发者使用 Node.js 22.19+(22.x)、24 或 25 和可用 npm 在全新检出中安装依赖
- **THEN** npm 和 Cargo MUST 使用已提交的锁文件解析确定的依赖版本
- **AND** 安装过程 MUST NOT 依赖用户全局安装的 TypeScript、构建器或测试框架

#### Scenario: 本地工具版本与记录版本不同

- **WHEN** 当前 Node.js major version 为 22 或 24,但 Node.js patch version 或 npm version与 CI/发布记录不同
- **WHEN** 当前 Node.js major version 为 22、24 或 25,但 Node.js patch version 或 npm version与 CI/发布记录不同
- **THEN** 本地开发和真实 Gate MUST允许继续运行
- **AND** 诊断 MAY记录实际版本但 MUST NOT把差异判定为失败或阻塞

#### Scenario: Node.js major version不兼容

- **WHEN** 当前 Node.js major version 不是 22 或 24
- **WHEN** 当前 Node.js major version 不是 22、24 或 25
- **THEN** 需要 Node.js 的开发命令 MUST明确报告兼容性错误

### Requirement: 架构一致的 Workspace 结构
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "module",
"packageManager": "npm@11.8.0",
"engines": {
"node": ">=22.19.0 <23.0.0 || >=24.0.0 <25.0.0"
"node": ">=22.19.0 <23.0.0 || >=24.0.0 <26.0.0"
},
"workspaces": [
"packages/*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export const REMOTE_FAILURE_CHINESE: Readonly<Record<string, string>> = Object.f
"当前 codexhost 与 npm 全局安装位置不匹配。请选择仅卸载远程服务,或在远程电脑上手动卸载软件包",
"The remote service was uninstalled, but removing the codexhost package failed. Check npm permissions on the remote computer and retry":
"远程服务已卸载,但 codexhost 软件包卸载失败。请检查远程电脑上的 npm 权限后重试",
"Remote Node.js is unsupported. Select Node.js 22.19 or later in the 22.x series, or Node.js 24.x":
"远程 Node.js 版本不受支持,请选择 Node.js 22.19 及以上的 22.x 版本,或 Node.js 24.x",
"Remote Node.js is unsupported. Select Node.js 22.19 or later in the 22.x series, or Node.js 24.x/25.x":
"远程 Node.js 版本不受支持,请选择 Node.js 22.19 及以上的 22.x 版本,或 Node.js 24.x/25.x",
"npm installation failed. Check network access and global installation permissions":
"npm 安装失败,请检查网络和全局安装权限",
"Remote service configuration failed. Check Codex CLI and the remote desktop login":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,13 @@ describe("remote failure messages", () => {
expect(localizeRemoteFailure(native, zh)).toBe(native);
expect(localizeRemoteFailure("远程服务已修复,正在连接", zh)).toBe("远程服务已修复,正在连接");
});

it("includes Node.js 25 in the supported remote runtime guidance", () => {
const message =
"Remote Node.js is unsupported. Select Node.js 22.19 or later in the 22.x series, or Node.js 24.x/25.x";
expect(localizeRemoteFailure(message, zh)).toBe(
"远程 Node.js 版本不受支持,请选择 Node.js 22.19 及以上的 22.x 版本,或 Node.js 24.x/25.x",
);
expect(localizeRemoteFailure(message, en)).toBe(message);
});
});
2 changes: 1 addition & 1 deletion scripts/release/prepare-npm-meta.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ ${NPM_PACKAGE_DESCRIPTION}
npm install -g ${NPM_PACKAGE_NAME}@${version}
\`\`\`

npm automatically installs the matching macOS, Windows, or Linux platform package. Node.js 22 or 24 and the official ChatGPT/Codex Desktop are required.
npm automatically installs the matching macOS, Windows, or Linux platform package. Node.js 22.19+ (22.x), 24 or 25 and the official ChatGPT/Codex Desktop are required.

## Usage

Expand Down
2 changes: 1 addition & 1 deletion scripts/release/prepare-npm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ The \`codexhost\` command launches the packaged Rust launcher with:

## Requirements

- Node.js 22 or 24 (Node 20 and older are not supported)
- Node.js 22.19+ (22.x), 24 or 25
- Official ChatGPT/Codex Desktop for macOS, Windows, or Linux
- Pi on \`PATH\` when using the Pi agent
- Claude Code installed when using the Claude Code adapter
Expand Down
4 changes: 2 additions & 2 deletions tools/dev-desktop/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,9 @@ function runLauncher(invocation, root, spawnImplementation = spawn, environment
});
}

// Node 22.19+ and Node 24 are supported development runtimes. The published npm
// Node 22.19+ (22.x), Node 24 and Node 25 are supported development runtimes. The published npm
// package also accepts newer host runtimes, but this repository keeps the local range bounded.
const supportedNodeMajors = [22, 24];
const supportedNodeMajors = [22, 24, 25];

function nodeVersionSupported(version = process.versions.node) {
const [majorText, minorText] = version.split(".");
Expand Down
12 changes: 6 additions & 6 deletions tools/dev-desktop/run.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ describe("development Desktop start", () => {
environment: {},
spawnImplementation: vi.fn(),
}),
).rejects.toThrow("npm start requires Node.js 22 or 24; current version is 20.19.0");
).rejects.toThrow("npm start requires Node.js 22 or 24 or 25; current version is 20.19.0");
} finally {
Object.defineProperty(process.versions, "node", {
value: originalNode,
Expand All @@ -329,7 +329,7 @@ describe("development Desktop start", () => {
}
});

it("accepts Node.js 22 and 24", async () => {
it("accepts Node.js 22, 24 and 25", async () => {
const root = temporaryDirectory();
const nodePath = path.join(root, "node.exe");
materializeArtifacts(root, "win32", nodePath);
Expand All @@ -339,7 +339,7 @@ describe("development Desktop start", () => {
vi.spyOn(console, "warn").mockImplementation(() => undefined);

try {
for (const version of ["22.19.0", "24.0.0"]) {
for (const version of ["22.19.0", "24.0.0", "25.0.0", "25.4.0"]) {
Object.defineProperty(process.versions, "node", {
value: version,
configurable: true,
Expand All @@ -363,11 +363,11 @@ describe("development Desktop start", () => {
}
});

it("rejects Node.js 23", async () => {
it.each(["22.18.0", "23.0.0", "26.0.0"])("rejects Node.js %s", async (version) => {
const root = temporaryDirectory();
const originalNode = process.versions.node;
Object.defineProperty(process.versions, "node", {
value: "23.0.0",
value: version,
configurable: true,
});
try {
Expand All @@ -379,7 +379,7 @@ describe("development Desktop start", () => {
environment: {},
spawnImplementation: vi.fn(),
}),
).rejects.toThrow("npm start requires Node.js 22 or 24; current version is 23.0.0");
).rejects.toThrow(`npm start requires Node.js 22 or 24 or 25; current version is ${version}`);
} finally {
Object.defineProperty(process.versions, "node", {
value: originalNode,
Expand Down
Loading