Skip to content

Build fails sporadically with cached compile step argument file #23993

Open
@ianprime0509

Description

@ianprime0509
Contributor

Zig Version

0.14.1 (also reproduces with 0.14.0)

Steps to Reproduce and Observed Behavior

  1. Clone https://github.com/ianprime0509/zig-build-args-repro (commit 707edbd38070b716afefff7bf1317671aff1bcbe).

  2. Run zig build. It should complete successfully.

  3. Run zig build again until it produces the following error:

    install
    └─ install zig_build_args_repro
       └─ zig build-exe zig_build_args_repro Debug native
          └─ zig build-lib SDL3_ttf Debug native
             └─ zig build-lib SDL3 Debug native failure
    error: error: expected a positional argument, -femit-bin=[path], --show-builtin, or --name [name]
    
    error: the following command exited with error code 1:
    /var/home/ian/.config/Code/User/globalStorage/ziglang.vscode-zig/zig/x86_64-linux-0.14.1/zig build-lib @/var/home/ian/Projects/zig-build-args-repro/.zig-cache/args/44a534baedbbd367216ee799c47b9d8c1307bf38b72dc56dea1bb37c9bb73948 
    Build Summary: 20/25 steps succeeded; 1 failed
    install transitive failure
    └─ install zig_build_args_repro transitive failure
       └─ zig build-exe zig_build_args_repro Debug native transitive failure
          ├─ zig build-lib SDL3_ttf Debug native transitive failure
          │  ├─ zig build-lib SDL3 Debug native failure
          │  └─ zig build-lib SDL3 Debug native (+3 more reused dependencies)
          └─ zig build-lib SDL3_ttf Debug native (+9 more reused dependencies)
    error: the following build command failed with exit code 1:
    /var/home/ian/Projects/zig-build-args-repro/.zig-cache/o/04842cd84a29465886c6e2298c6840f8/build /var/home/ian/.config/Code/User/globalStorage/ziglang.vscode-zig/zig/x86_64-linux-0.14.1/zig /var/home/ian/.config/Code/User/globalStorage/ziglang.vscode-zig/zig/x86_64-linux-0.14.1/lib /var/home/ian/Projects/zig-build-args-repro /var/home/ian/Projects/zig-build-args-repro/.zig-cache /var/home/ian/.cache/zig --seed 0x309ea898 -Z30a6c0f54a1dacc0
    

    The error does not occur with every re-run of zig build, although it does happen most of the time on my system.

Note that deleting the directory .zig-cache/args before re-running zig build always fixes the issue for the next build, but it will occur again for subsequent builds, once the first build repopulates .zig-cache/args.

In case this issue is system-dependent (e.g. conditions for creating an args file), here is my uname -a:

Linux ian-laptop 6.14.3-300.fc42.x86_64 #1 SMP PREEMPT_DYNAMIC Sun Apr 20 16:08:39 UTC 2025 x86_64 GNU/Linux

Expected Behavior

The second zig build command should succeed without errors, as the first one did.

Activity

added
bugObserved behavior contradicts documented or intended behavior
on May 26, 2025
alexrp

alexrp commented on May 26, 2025

@alexrp
SponsorMember

Did this happen in 0.14.0?

cc @mlugg

ianprime0509

ianprime0509 commented on May 26, 2025

@ianprime0509
SponsorContributorAuthor

Yes, the same issue occurs with 0.14.0 as well. This is reduced from a new project, so I hadn't originally tested with that version, but I'll edit the issue description to make it clear that it's not a 0.14.1 regression.

ianprime0509

ianprime0509 commented on May 26, 2025

@ianprime0509
SponsorContributorAuthor

My suspicion for what's happening here is a race condition in this logic:

const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash;
try b.cache_root.handle.writeFile(.{ .sub_path = args_file, .data = args });
Since writeFile is not atomic, and will truncate an existing file before writing, it is possible for two compile steps with the same arguments to race in creating the same arguments file, such that the first compiler process tries to read from it at the same time that the second compile step has truncated the file to write to it again, leading to the first compiler process erroneously receiving no arguments. That scenario would reproduce the original error message observed ("error: expected a positional argument, -femit-bin=[path], --show-builtin, or --name [name]").

Edit: my suspicion is strengthened by this updated logic solving the problem for me:

const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash;
b.cache_root.handle.writeFile(.{
    .sub_path = args_file,
    .data = args,
    .flags = .{ .exclusive = true },
}) catch |err| switch (err) {
    error.PathAlreadyExists => {},
    else => |other_err| return other_err,
};

But this still isn't 100% correct, because it only prevents the truncation part of the race condition. It would still be possible for another compile step to read a partially written args file from the first creation attempt with this patch. Most likely a fully correct fix would involve writing the arguments into a temporary file with a random path and renaming it to the correct place (which is what several other compiler processes, such as package fetching, do).

added
zig build systemstd.Build, the build runner, `zig build` subcommand, package management
on May 26, 2025
added this to the 0.15.0 milestone on May 26, 2025
added a commit that references this issue on May 26, 2025
ddcc7de
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behaviorzig build systemstd.Build, the build runner, `zig build` subcommand, package management

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      Participants

      @alexrp@ianprime0509

      Issue actions

        Build fails sporadically with cached compile step argument file · Issue #23993 · ziglang/zig