Disable js_repl when Node is incompatible at startup (#12824)

## Summary
- validate `js_repl` Node compatibility during session startup when the
experiment is enabled
- if Node is missing or too old, disable `js_repl` and
`js_repl_tools_only` for the session before tools and instructions are
built
- surface that startup disablement to users through the existing startup
warning flow instead of only logging it
- reuse the same compatibility check in js_repl kernel startup so
startup gating and runtime behavior stay aligned
- add a regression test that verifies the warning is emitted and that
the first advertised tool list omits `js_repl` and `js_repl_reset` when
Node is incompatible

## Why
Today `js_repl` can be advertised based only on the feature flag, then
fail later when the kernel starts. That makes the available tool list
inaccurate at the start of a conversation, and users do not get a clear
explanation for why the tool is unavailable.

This change makes tool availability reflect real startup checks, keeps
the advertised tool set stable for the lifetime of the session, and
gives users a visible warning when `js_repl` is disabled.

## Testing
- `just fmt`
- `cargo test -p codex-core --test all
js_repl_is_not_advertised_when_startup_node_is_incompatible`
This commit is contained in:
Curtis 'Fjord' Hawthorne
2026-02-25 17:14:51 -08:00
committed by GitHub
parent 14116ade8d
commit 40ab71a985
3 changed files with 126 additions and 4 deletions

View File

@@ -556,10 +556,7 @@ impl JsReplManager {
turn: Arc<TurnContext>,
thread_id: Option<ThreadId>,
) -> Result<KernelState, String> {
let node_path = resolve_node(self.node_path.as_deref()).ok_or_else(|| {
"Node runtime not found; install Node or set CODEX_JS_REPL_NODE_PATH".to_string()
})?;
ensure_node_version(&node_path).await?;
let node_path = resolve_compatible_node(self.node_path.as_deref()).await?;
let kernel_path = self
.write_kernel_script()
@@ -1321,6 +1318,14 @@ async fn ensure_node_version(node_path: &Path) -> Result<(), String> {
Ok(())
}
pub(crate) async fn resolve_compatible_node(config_path: Option<&Path>) -> Result<PathBuf, String> {
let node_path = resolve_node(config_path).ok_or_else(|| {
"Node runtime not found; install Node or set CODEX_JS_REPL_NODE_PATH".to_string()
})?;
ensure_node_version(&node_path).await?;
Ok(node_path)
}
pub(crate) fn resolve_node(config_path: Option<&Path>) -> Option<PathBuf> {
if let Some(path) = std::env::var_os("CODEX_JS_REPL_NODE_PATH") {
let p = PathBuf::from(path);