Skip to content

fix(linker): richer diagnostic for subprocess spawn failures (Windows long-cmdline / long-path)#1722

Open
ghaith wants to merge 2 commits intomasterfrom
fix/linker-spawn-diagnostic-1721
Open

fix(linker): richer diagnostic for subprocess spawn failures (Windows long-cmdline / long-path)#1722
ghaith wants to merge 2 commits intomasterfrom
fix/linker-spawn-diagnostic-1721

Conversation

@ghaith
Copy link
Copy Markdown
Collaborator

@ghaith ghaith commented May 6, 2026

Note: this description was drafted by Claude (via Claude Code) and posted on my behalf. Verify before merging. — Ghaith

Summary

Refs #1721diagnostic step only, not the mitigation.

When the linker subprocess fails to start, the current diagnostic is the flat An error occurred during linking: {io::Error} (E077). For Windows hosts hitting os error 206 (ERROR_FILENAME_EXCED_RANGE) — the user-reported symptom in #1721 — that message is too terse to be actionable, and 206 covers two distinct conditions that can't be disambiguated from the bare error code:

  1. CreateProcess rejecting an assembled command line longer than 32,767 bytes (or 8,191 if going through cmd.exe).
  2. CreateFile rejecting a single path longer than MAX_PATH (260 chars) that is not prefixed with \\?\.

This PR adds a small diagnose_spawn_error helper that wraps the failing Command::status() with structured context. It does not change the LinkerError schema (still Link(String) → E077), nor does it ship the actual mitigation (response file, \\?\ prefix). The mitigation will land separately once field reports show which failure mode dominates — see #1721 for the two-step plan.

What this PR does

  • New free function diagnose_spawn_error(err: &io::Error, linker: &Path, args: &[String]) -> LinkerError in src/linker.rs.
  • The ? on Command::new(...).args(...).status() in CcLinker::finalize is replaced with explicit error mapping that funnels through the helper.
  • On any host: the diagnostic now includes the linker path, arg count, approximate command-line length, and the longest individual argument with its length. The "approximate" qualifier is honest — we don't reimplement CommandLineToArgvW quoting; the figure is meant to give the user a sense of scale.
  • On Windows specifically when err.raw_os_error() == Some(206): appends a section listing the two possible causes (cmdline > 32K vs single path > MAX_PATH) and two user-side workarounds (move workspace closer to drive root, enable LongPathsEnabled).

Sample output

Linux, generic PermissionDenied (snapshot-pinned by the new test):

An error occurred during linking: permission denied
Diagnostic context:
  - linker:          /usr/bin/ld.lld
  - args:            3
  - cmdline length:  19 bytes
  - longest arg:     12 bytes ('longer-arg-2')

Windows, os error 206 (snapshot-pinned by the #[cfg(windows)] test):

An error occurred during linking: [os-error 206 (locale-dependent)]
Diagnostic context:
  - linker:          ld.lld.exe
  - args:            2
  - cmdline length:  7 bytes
  - longest arg:     3 bytes ('one')
Possible causes on Windows (ERROR_FILENAME_EXCED_RANGE):
  - The assembled command line exceeded 32,767 bytes (CreateProcess limit).
  - A single argument exceeded MAX_PATH (260 chars) without the \\?\ prefix.
Possible workarounds:
  - Move the workspace closer to the drive root to shorten paths.
  - Enable Windows long paths (LongPathsEnabled registry / process manifest).

The Windows test uses an insta filter on the locale-dependent first line because the underlying io::Error display is localized by FormatMessage.

Tests

  • spawn_diagnostic_for_generic_io_error — synthetic PermissionDenied + 3 args; pins the generic message shape.
  • spawn_diagnostic_for_os_error_206_includes_windows_section (#[cfg(windows)]) — synthetic from_raw_os_error(206); pins the full Windows-specific message via a locale-tolerant filter.

The helper is a pure function over (io::Error, &Path, &[String]), so the tests don't spawn any subprocess.

Out of scope

When the linker subprocess fails to start, the previous diagnostic was
just `An error occurred during linking: {io::Error}` (E077) — too terse
to act on. Most notably, Windows raises `os error 206`
(`ERROR_FILENAME_EXCED_RANGE`) for two distinct conditions —
`CreateProcess` rejecting an assembled command line longer than 32,767
bytes, or `CreateFile` rejecting a single path past `MAX_PATH` without
the `\\?\` prefix — and we couldn't tell which.

Now the spawn-failure path goes through a `diagnose_spawn_error` helper
that includes:

- linker path
- arg count
- approximate command-line length (sum of arg byte-lengths plus
  separators; not a faithful CommandLineToArgvW reimplementation)
- longest individual argument and its length

On Windows additionally, when `raw_os_error() == Some(206)`, the
diagnostic appends a Windows-specific section listing the two possible
causes and two user-side workarounds (move the workspace closer to the
drive root; enable `LongPathsEnabled`).

The schema-level error (`LinkerError::Link(String)` → E077) is
unchanged — only the message string is richer.

Refs #1721

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Build Artifacts

🐧 Linux

Artifact Link Size
deb-x86_64 Download 38.5 MB
schema Download 0.0 MB
stdlib Download 32.4 MB
plc-x86_64 Download 43.6 MB
deb-aarch64 Download 30.9 MB
plc-aarch64 Download 43.5 MB

From workflow run

🪟 Windows

Artifact Link Size
stdlib.lib Download 4.3 MB
stdlib.dll Download 0.1 MB
plc.exe Download 38.4 MB

From workflow run

The Windows-only spawn_diagnostic_for_os_error_206_includes_windows_section
test passed ["one", "two"]. Both are 3 bytes and max_by_key returns the last
element on ties, so the actual longest arg was 'two' while the snapshot
asserted 'one', failing the Windows CI lane. Use ["one", "three"] so the
longest is unambiguous on every platform.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant