Skip to content

daemon binds tool resolution to spawn-time PATH for up to 12h — bare-name esptool/python/objcopy spawns resolve nondeterministically #1219

Description

@zackees

Split out of #1217, which was the esptool-specific symptom. This is the underlying class.

Problem

Tool resolution for bare-name spawns is bound to whichever environment happened to spawn the daemon, and that daemon then lives for up to 12 hours. Two builds with byte-identical inputs can resolve different toolchains depending only on which shell started the daemon and when.

The binding happens once, at spawncrates/fbuild-cli/src/daemon_client.rs:1038:

cmd.env_clear().envs(baseline);
if let Some(path) = launcher_path(std::env::vars_os()) {
    cmd.env("PATH", path);
}
// then re-adds only vars starting with FBUILD_

launcher_path (daemon_client.rs:1006) correctly forwards the launching CLI's PATH. But builds execute inside the daemon, so every subsequent CLI invocation that connects to an already-running daemon inherits the first launcher's PATH, not its own. Nothing re-syncs it per request.

The window is longcrates/fbuild-daemon/src/context.rs:108:

/// Fallback idle timeout: daemon shuts down after 12 hours regardless.
pub const IDLE_TIMEOUT: Duration = Duration::from_secs(43200);

What actually resolves from PATH inside the daemon

Not a corner case — these are all bare-name spawns with no absolute path:

Site Spawns Notes
fbuild-build-esp/src/esp32/esp32_linker.rs:73 esptool fallback when provisioning returns None — the #1217 outage
fbuild-build-esp/src/esp8266/esp8266_linker.rs:263 esptool unconditional; no provisioned path attempted at all
fbuild-build-engine/src/script_runtime.rs:303-305 python3 / python / py -3 find_python() probes candidates in PATH order
fbuild-build-esp/src/esp32/orchestrator/boot_artifacts.rs:149 python fallback
fbuild-build-mcu/src/ch32v/ch32v_linker.rs:238 objcopy

find_python() is the one I'd weight highest. Which interpreter runs PlatformIO build scripts — a project venv's or the system's — currently depends on daemon spawn lineage. That is a silent correctness hazard, not just an availability one: the build succeeds, it just ran under an interpreter nobody selected.

Why this is worth fixing beyond #1217

#1217's fix (PR #1218) makes esptool provisioning succeed, so the PATH fallback stops being reached for ESP32. It does not make the fallback correct. Any future URL-shape change, a provisioning 404, an unsupported host tag — all still land on a PATH lookup against a possibly-stale environment, and fail minutes into a build with a misleading message.

It also makes builds non-reproducible in a way that is genuinely hard to debug: the same command, same repo, same inputs, different result, with the only variable being whether a daemon from this morning is still alive.

Suggested direction

  1. Forward the caller's PATH (or resolved tool paths) per request, not only at spawn. The build request already carries project context; the caller's execution environment is arguably part of it.
  2. Failing that, detect drift — if a request arrives whose caller PATH differs from the daemon's, either re-exec, refuse with a clear message, or at minimum log it loudly. Silent divergence is the worst of the three.
  3. Consider whether bare-name spawns should exist at all in the build path. Every one of them is a place where the daemon's environment silently becomes part of the build inputs.

Repro sketch

  1. Start a daemon from a shell without a project venv on PATH (any fbuild invocation does this).
  2. Activate the venv in a second shell; confirm which esptool / which python3 resolve inside it.
  3. Run a build from the second shell.
  4. The daemon still resolves the first shell's PATH; the venv tools are invisible. Kill the daemon and re-run to see the result change with no input change.

This is the mechanism behind the FastLED CI outage in #1217: GitHub Actions installs esptool into the project venv (esptool>=5.0.2), but the daemon's PATH did not include it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions