Skip to content

Exported environment variables are silently dropped from the child process when declared out of name order #183

Description

@typeless

Summary

build_env_cache sorts its names by StringId handle, and env_cache_find binary-searches the result by name:

// src/exec/scheduler.cpp:62 — handle order, i.e. interning order
std::sort(names.begin(), names.end());

// src/exec/scheduler.cpp:44 — a name comparator over that same range
auto pos = std::lower_bound(cache.begin(), cache.end(), key,
    [&pool](auto const& p, std::string_view k) { return pool.get(p.first) < k; });

lower_bound requires the range to be partitioned with respect to the comparator it is given. The range is ordered by handle and searched by name, so the lookup returns nullptr for entries that are present. prepare_job_launch (src/exec/scheduler.cpp:129-137) then omits the variable, and since base_child_env() contributes only PATH=, the variable is absent from the child process entirely.

Interning order here is the order the export directives were parsed, so the bug is triggered by the order the exports are written in the Tupfile.

Reproduced

Two projects, identical except for the order of three export lines. Rule is : |> sh -c "env | sort > %o" |> out.txt, both run with ZVAR=zzz MVAR=mmm AVAR=aaa:

export AVAR / export MVAR / export ZVAR   ->  3 of 3 present: AVAR=aaa MVAR=mmm ZVAR=zzz
export ZVAR / export MVAR / export AVAR   ->  0 of 3 present

Both builds exit 0 and report success. The command simply runs with a silently wrong environment.

A single exported variable always works, because lower_bound over a one-element range cannot be wrong — which is why test/unit/test_exec.cpp:437 ("Scheduler exported_vars", one variable) passes today.

Severity

Silent wrong build output, no diagnostic, exit 0. It contradicts what docs/reference.md:1099-1101 promises for an exported variable.

Fix

One comparator must serve both the sort and the search: sort with the same name projection the lookup uses, or give EnvCache a lookup that owns its ordering. Note that job.exported_vars is a SortedIdVec (handle-ordered), so the std::sort at :62 is nearly a no-op today — fixing only that line is not enough unless the projection is applied there.

The general enforcement point is the deleted-relational-operator change proposed separately: a container ordered by handle and searched by name is a mistake the compiler can catch.

Provenance

Found by adversarial multi-agent review of PR #176, whose body wrongly cleared this site as "order-agnostic by construction". Pre-existing on main — not introduced by any open PR. Reproduced independently before filing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions