SIGN IN SIGN UP

fix(lint): handle BES/remote-download race for lint output files (#1148)

With remote execution, Bazel streams `named_set_of_files` BES events as
soon as an action completes on the remote executor — before
`--remote_download_regex` transfers have completed locally. The lint BES
loop was calling `read_to_string` immediately on receipt of the event,
causing a fatal `No such file or directory` crash.

Root cause confirmed via Bazel source: `RemoteExecutionService.java`
downloads blobs to a unique temp path, then atomically renames to the
final location via `FileSystemUtils.moveFile`
(`moveOutputsToFinalLocation` L930). `fs.exists()` returning `True`
therefore means the file is fully written with no partial-read risk.
See:
https://github.com/bazelbuild/bazel/blob/d3c9a2080e30cecd661c02a8f0cce84caa696aed/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java#L930

**Changes in this PR:**

- All lint file reads (`.out`, `.report`, `.patch`, `.exit_code`) are
routed through `_try_process_lint_file`, which guards with `fs.exists()`
and returns `None` when the file isn't on disk yet. Not-yet-available
files are queued in `pending_files` and retried each 250 ms tick via a
shared `_drain_pending_files` helper used by both the tick loop and the
post-build drain.
- After `build.wait()`, a bounded post-build drain polls for up to 30
seconds before warning and skipping any files that still haven't landed.
- `rules_lint_human` and `rules_lint_machine` are separate action sets
in rules_lint — requesting both runs each linter twice. We now pick
exactly one: interactive (local TTY, not CI) → `rules_lint_human`;
everything else → `rules_lint_machine`. Non-interactive / CI runs always
get `rules_lint_machine`, preventing a silent "no lint results" failure
when no hooks requested machine output.
- `detect_ci()` is checked alongside `is_tty`: some CI hosts (e.g.
GitHub Actions) allocate a pseudo-TTY, making `is_tty` True while the
correct output is still machine.

---

### Changes are visible to end-users: yes

- Searched for relevant documentation and updated as needed: no
- Breaking change (forces users to change their own code or config): no
- Suggested release notes appear below: yes

Previously, `aspect lint` could crash with a fatal `No such file or
directory` error when using remote execution, because BES events for
lint output files can arrive before Bazel finishes downloading them. The
lint task now queues those files and retries each tick, with a 30-second
timeout after the build completes. Also fixes: `.patch` files were
subject to the same race; non-TTY/CI runs now always select
`rules_lint_machine` output (previously could silently produce no output
groups and exit with a misleading "no lint results" error);
`detect_ci()` prevents CI hosts with pseudo-TTYs from incorrectly
selecting human output.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
G
Greg Magolan committed
5b667cb249819360d964fdcf0e572687cc233902
Parent: 1f62b9a
Committed by GitHub <noreply@github.com> on 5/27/2026, 6:48:10 PM