Skip to content

daemon: URL-sourced custom node (no path_sha256) downloads to a relative build/ under the daemon cwd, but the child execs relative to node_working_dir → spawn fails (ENOENT) #2508

Description

@phil-opp

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcliCLIdaemondocumentationImprovements or additions to documentationrust

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions