Skip to content

fix: avoid Windows arg-length limits in git check-attr and Scope::Files checks#390

Merged
zeitlinger merged 3 commits into
mainfrom
fix/windows-check-attr-arg-length
Jul 13, 2026
Merged

fix: avoid Windows arg-length limits in git check-attr and Scope::Files checks#390
zeitlinger merged 3 commits into
mainfrom
fix/windows-check-attr-arg-length

Conversation

@zeitlinger

@zeitlinger zeitlinger commented Jul 11, 2026

Copy link
Copy Markdown
Member

Summary

Found by @trask on Windows: open-telemetry/opentelemetry-java-instrumentation#19183 (comment)

$ mise run lint:fix
[lint:fix] $ flint run --fix
Error: git check-attr

Caused by:
    The filename or extension is too long. (os error 206)

Two fixes for the same underlying bug class — passing many/long file paths as CLI args with no (or an inadequate) length cap, overflowing Windows' command-line length limits.

1. git check-attr (internal git-plumbing helper)

  • generated_paths() passed candidate paths as CLI args to git check-attr, batched at 256 per invocation. On repos with many/long paths (e.g. opentelemetry-java-instrumentation, a deeply-nested Java monorepo) a batch can total tens of KB, exceeding Windows' ~32KB CreateProcess command-line limit (os error 206, ERROR_FILENAME_EXCED_RANGE — yes, that's the real Win32 constant's actual spelling; added a _typos.toml entry so typos stops "fixing" it to "EXCEED").
  • Switches to git check-attr --stdin -z, writing paths to the child's stdin instead of argv — stdin has no OS command-line-length ceiling, so batching is no longer needed. Paths are written on a separate thread concurrently with reading stdout/stderr, since output can fill the OS pipe buffer before all paths are written, which would deadlock if done sequentially.

2. Scope::Files check invocations (general check-execution path)

While investigating, found the same bug class in build_invocations() — worse, actually, since it had no length cap at all (not even a fixed batch count):

  • {FILES}/{RELFILES} was substituted with every matched file joined into one command. Many Scope::Files checks are mise shims routed through cmd.exe /C by spawn_command, and cmd.exe's own command-line buffer caps at ~8191 chars — tighter than CreateProcessW's ~32KB.
  • google-java-format (registered via Check::files, no full_cmd fallback) is directly exposed: opentelemetry-java-instrumentation has thousands of .java files with long nested paths, so a PR touching as few as ~50 matching files could already overflow the limit. ryl, zizmor, xmllint, typos, and editorconfig-checker are similarly exposed. ktlint/dotnet-format have a full_cmd fallback, but it only applies to whole-project ("full") runs, not the common PR-scoped changed-files case.
  • Adds chunk_files_by_length(), splitting matched files into groups that keep the rendered {FILES}/{RELFILES} argument under a conservative 6000-char cap, issuing multiple invocations instead of one unbounded one. run_invocations() already loops over a list of invocations and combines their output, so no changes were needed there.

Test plan

  • check-attr: regression test with long-enough paths that a single 256-path argv batch would total ~56KB (verified mathematically, comfortable margin over the 32KB limit)
  • Scope::Files: regression test constructing a check with enough long-named files that a single invocation would exceed the cap, verifying build_invocations splits into multiple invocations that together cover every matched file exactly once
  • Both: this crate's own CI matrix (.github/workflows/test.yml) already runs cargo test on windows-2025, so these tests exercise the real failure mode there — can't reproduce the literal Windows OS error from a Linux sandbox, so margins were verified by calculation rather than direct repro
  • cargo build, cargo test (272/273 pass; the one failure is a pre-existing environment gap — missing linter binaries on PATH in this sandbox, unrelated)
  • cargo fmt --check, cargo clippy --all-targets -- -D warnings
  • mise exec -- typos . clean

generated_paths() passed candidate paths as CLI args to `git
check-attr`, batched at 256 per invocation. On repos with many/long
paths this batch can total tens of KB, exceeding Windows' ~32KB
CreateProcess command-line limit (os error 206,
ERROR_FILENAME_EXCED_RANGE) even though it's nowhere near Linux/macOS
argv limits — this is exactly what broke for a user on a large,
deeply-nested Java monorepo.

Switch to `git check-attr --stdin -z`, writing paths to the child's
stdin instead of argv. Stdin has no OS command-line-length ceiling, so
batching is no longer needed. Paths are written on a separate thread
concurrently with reading stdout/stderr, since output can fill the OS
pipe buffer well before all paths are written for a large repo, which
would deadlock if done sequentially.

Adds a regression test with long-enough paths that a single 256-path
argv batch would total ~56KB, well over Windows' 32KB limit — this
crate's own CI matrix runs `cargo test` on windows-2025, so this test
exercises the actual failure mode there (Linux/macOS argv limits are
much higher and wouldn't have caught this).
Signed-off-by: Gregor Zeitlinger <[email protected]>
@zeitlinger
zeitlinger marked this pull request as ready for review July 11, 2026 09:06
@zeitlinger
zeitlinger requested a review from a team as a code owner July 11, 2026 09:06
Copilot AI review requested due to automatic review settings July 11, 2026 09:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Flint’s git check-attr integration to avoid Windows’ CreateProcess command-line length limit by switching from argv-based batching to streaming paths via stdin, and adds a regression test to ensure the issue doesn’t recur.

Changes:

  • Run git check-attr --stdin -z and write candidate paths to stdin (instead of passing many paths as CLI args).
  • Add a regression test covering many long paths to exercise the Windows failure mode in CI.
  • Add _typos.toml allowlist entry for the genuine Win32 constant spelling EXCED.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/files.rs Switch git check-attr invocation to stdin streaming and add regression test for long-path scenarios.
_typos.toml Prevent typos from “correcting” the genuine Win32 EXCED spelling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/files.rs Outdated
Comment thread src/files.rs Outdated
…imits

Same bug class as the git check-attr fix in this PR: build_invocations()
substituted {FILES}/{RELFILES} with every matched file joined into a
single command, with no length cap at all (not even a fixed batch
count). On Windows this is worse than the check-attr case, since many
Scope::Files checks are mise shims routed through `cmd.exe /C` by
spawn_command, and cmd.exe's own command-line buffer caps at ~8191
chars — tighter than CreateProcessW's ~32KB.

google-java-format is registered via Check::files with no full_cmd
fallback, and opentelemetry-java-instrumentation (where the check-attr
bug was found) has thousands of .java files with long nested paths —
a PR touching as few as ~50 matching files could already overflow the
cmd.exe limit. ryl, zizmor, xmllint, typos, and editorconfig-checker
are similarly exposed. ktlint and dotnet-format have a full_cmd
fallback, but it only applies to whole-project ("full") runs, not the
common PR-scoped changed-files case.

Adds chunk_files_by_length(), splitting matched files into groups that
keep the rendered {FILES}/{RELFILES} argument under a conservative cap
(6000 chars), issuing multiple invocations instead of one unbounded
one. run_invocations() already loops over a list of invocations and
combines their output, so no changes were needed there.

Regression test constructs a Scope::Files check with enough long-named
files that a single invocation would exceed the cap, and verifies
build_invocations splits into multiple invocations that together
cover every matched file exactly once.
Signed-off-by: Gregor Zeitlinger <[email protected]>
@zeitlinger zeitlinger changed the title fix: avoid CreateProcess arg-length limit in git check-attr on Windows fix: avoid Windows arg-length limits in git check-attr and Scope::Files checks Jul 11, 2026
- Always join the stdin-writer thread in generated_paths(), even when
  wait_with_output() errors, so it's never left detached.
- Rewrite the check-attr regression test to call generated_paths()
  directly against synthetic (non-existent-on-disk) long paths instead
  of real files, avoiding Windows' legacy 260-char MAX_PATH limit on CI
  runners while still exercising the actual git check-attr subprocess
  with long paths via stdin.
Signed-off-by: Gregor Zeitlinger <[email protected]>
@zeitlinger
zeitlinger merged commit d8450ff into main Jul 13, 2026
14 checks passed
@zeitlinger
zeitlinger deleted the fix/windows-check-attr-arg-length branch July 13, 2026 09:57
@github-actions github-actions Bot mentioned this pull request Jul 13, 2026
zeitlinger pushed a commit that referenced this pull request Jul 13, 2026
## 🤖 New release

* `flint`: 0.22.7 -> 0.22.8

<details><summary><i><b>Changelog</b></i></summary><p>

<blockquote>

## [0.22.8](v0.22.7...v0.22.8)
- 2026-07-13

### Fixed

- avoid Windows arg-length limits in git check-attr and Scope::Files
checks ([#390](#390))

### Other

- *(deps)* lock file maintenance
([#397](#397))
- *(deps)* update dependency mise to v2026.7.5
([#395](#395))
- *(deps)* update linters
([#396](#396))
- *(deps)* update taiki-e/install-action action to v2.83.0
([#394](#394))
- *(deps)* update dependency rust to v1.97.0
([#392](#392))
- *(deps)* update taiki-e/install-action action to v2.82.11
([#391](#391))
- *(deps)* update dependency go to v1.26.5
([#389](#389))
- *(deps)* update taiki-e/install-action action to v2.82.10
([#388](#388))
- *(deps)* update taiki-e/install-action action to v2.82.9
([#387](#387))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
zeitlinger added a commit to zeitlinger/opentelemetry-java-instrumentation that referenced this pull request Jul 13, 2026
v0.22.8 includes grafana/flint#390, fixing `mise run lint:fix` crashing
on Windows with "The filename or extension is too long" (os error 206)
in git check-attr and Scope::Files checks (google-java-format, ryl,
zizmor, xmllint, typos, editorconfig-checker).

Signed-off-by: Gregor Zeitlinger <[email protected]>
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.

4 participants