Note: This issue was created by a scheduled automated Claude code-review check. It was found by randomly selecting a component (daemon/src/spawn) and reviewing it for correctness issues. Please treat the severity assessment as a starting point for human triage, not a confirmed defect.
Summary
When a custom node's path is a URL without a path_sha256 (a downloadable shared library / binary, permit_url enabled), the daemon downloads it into the relative path build/<filename> — resolved against the daemon process's cwd — and returns that relative path as the program to exec. But the spawned child's working directory is set to the node's node_working_dir, which is generally not the daemon's cwd. Because the program path contains a separator, the OS resolves it against the child's cwd, so the exec looks for <node_working_dir>/build/<filename> while the file was written to <daemon_cwd>/build/<filename> → the node fails to spawn with ENOENT (or, worse, execs a different file that happens to exist at that relative path under node_working_dir).
This is exactly the defect that the adjacent path_sha256 branch was written to avoid — but the non-sha256 URL branch was not given the same fix.
Affected code
binaries/daemon/src/spawn/command.rs
Buggy branch (relative download dir, relative return path):
// command.rs:101-109
} else if source_is_url(source) {
if !permit_url {
eyre::bail!("URL paths are not supported in this case");
}
// try to download the shared library
let target_dir = Path::new("build"); // <-- relative to the DAEMON cwd
download_file(source, target_dir, None)
.await
.wrap_err("failed to download custom node")?
}
download_file creates target_dir and returns target_dir.join(filename) (see libraries/extensions/download/src/lib.rs), i.e. the relative path build/<filename>. That becomes resolved_path, used directly as the program:
- non-
.py: Command::new(&resolved_path) (command.rs:~205)
.py: cmd.arg(&resolved_path) (command.rs:~195)
The sibling path_sha256 branch already documents and fixes this exact problem:
// command.rs:79-90
// Download under the node's own working dir and return an
// absolute path: the spawned child's cwd is set to `working_dir`,
// so a relative download path (resolved against the daemon's cwd)
// would exec the wrong file or fail to find it. ...
let target_dir = working_dir.join("build").join(expected_sha256);
let downloaded = download_file(source, &target_dir, Some(expected_sha256)).await?;
downloaded.canonicalize().unwrap_or(downloaded)
The child cwd is set in binaries/daemon/src/spawn/spawner.rs:223:
command = command.current_dir(&node_working_dir);
Reachability
node_working_dir is derived in binaries/daemon/src/lib.rs:2996-3011 from the configured per-node dir (e.g. a git clone directory), else base_working_dir.join(deploy.working_dir), else base_working_dir — none of which is tied to the daemon process's cwd. So node_working_dir != daemon_cwd is the normal case (and always the case for git-sourced nodes), which means a URL-sourced custom node run in any such dataflow hits the mismatch. Every other branch in this match returns an absolute path (the sha256 branch canonicalizes; resolve_path/resolve_path_confined resolve against working_dir) — only this one returns a path relative to the daemon cwd.
Impact
- Best case: node fails to spawn with a confusing "no such file" error even though the download succeeded.
- Worst case: if a file named
build/<filename> exists under node_working_dir, the daemon execs that unintended file instead of the freshly downloaded artifact.
Suggested fix
Mirror the sha256 branch — download under the node's working dir and return an absolute path:
} else if source_is_url(source) {
if !permit_url {
eyre::bail!("URL paths are not supported in this case");
}
let target_dir = working_dir.join("build");
let downloaded = download_file(source, &target_dir, None)
.await
.wrap_err("failed to download custom node")?;
downloaded.canonicalize().unwrap_or(downloaded)
}
(Content-addressed scoping by sha256 isn't available here, so a shared build/ dir may collide on filename across nodes sharing a working_dir — worth a follow-up, but the primary fix is making the path absolute and rooted at working_dir.)
Affected files
binaries/daemon/src/spawn/command.rs (URL branch, lines ~101-109; cf. sha256 branch ~79-90)
binaries/daemon/src/spawn/spawner.rs (child cwd, line ~223)
binaries/daemon/src/lib.rs (node_working_dir derivation, lines ~2996-3011)
Summary
When a custom node's
pathis a URL without apath_sha256(a downloadable shared library / binary,permit_urlenabled), the daemon downloads it into the relative pathbuild/<filename>— resolved against the daemon process's cwd — and returns that relative path as the program to exec. But the spawned child's working directory is set to the node'snode_working_dir, which is generally not the daemon's cwd. Because the program path contains a separator, the OS resolves it against the child's cwd, so the exec looks for<node_working_dir>/build/<filename>while the file was written to<daemon_cwd>/build/<filename>→ the node fails to spawn with ENOENT (or, worse, execs a different file that happens to exist at that relative path undernode_working_dir).This is exactly the defect that the adjacent
path_sha256branch was written to avoid — but the non-sha256 URL branch was not given the same fix.Affected code
binaries/daemon/src/spawn/command.rsBuggy branch (relative download dir, relative return path):
download_filecreatestarget_dirand returnstarget_dir.join(filename)(seelibraries/extensions/download/src/lib.rs), i.e. the relative pathbuild/<filename>. That becomesresolved_path, used directly as the program:.py:Command::new(&resolved_path)(command.rs:~205).py:cmd.arg(&resolved_path)(command.rs:~195)The sibling
path_sha256branch already documents and fixes this exact problem:The child cwd is set in
binaries/daemon/src/spawn/spawner.rs:223:Reachability
node_working_diris derived inbinaries/daemon/src/lib.rs:2996-3011from the configured per-node dir (e.g. a git clone directory), elsebase_working_dir.join(deploy.working_dir), elsebase_working_dir— none of which is tied to the daemon process's cwd. Sonode_working_dir != daemon_cwdis the normal case (and always the case for git-sourced nodes), which means a URL-sourced custom node run in any such dataflow hits the mismatch. Every other branch in thismatchreturns an absolute path (the sha256 branch canonicalizes;resolve_path/resolve_path_confinedresolve againstworking_dir) — only this one returns a path relative to the daemon cwd.Impact
build/<filename>exists undernode_working_dir, the daemon execs that unintended file instead of the freshly downloaded artifact.Suggested fix
Mirror the sha256 branch — download under the node's working dir and return an absolute path:
(Content-addressed scoping by sha256 isn't available here, so a shared
build/dir may collide on filename across nodes sharing aworking_dir— worth a follow-up, but the primary fix is making the path absolute and rooted atworking_dir.)Affected files
binaries/daemon/src/spawn/command.rs(URL branch, lines ~101-109; cf. sha256 branch ~79-90)binaries/daemon/src/spawn/spawner.rs(child cwd, line ~223)binaries/daemon/src/lib.rs(node_working_dirderivation, lines ~2996-3011)