Skip to content

fix(lint): resolve BES files via path_prefix; skip unreadable outputs gracefully#1135

Draft
gregmagolan wants to merge 2 commits into
mainfrom
fix-lint-skip-unreadable-bes-outputs
Draft

fix(lint): resolve BES files via path_prefix; skip unreadable outputs gracefully#1135
gregmagolan wants to merge 2 commits into
mainfrom
fix-lint-skip-unreadable-bes-outputs

Conversation

@gregmagolan
Copy link
Copy Markdown
Member

Summary

aspect lint crashed with No such file or directory (os error 2) partway through the BES drain on non-Aspect-Workflows CI hosts (example: bazel-examples GHA run). Two issues in the existing BES file handling:

  1. Resolution went straight to file_uri_to_path which only understands the canonical bytestream:// URI form and resolves it to a bb_clientd FUSE path (/mnt/ephemeral/buildbarn/bb_clientd/...) that only exists on Workflows runners.
  2. The subsequent read_to_string had no guard against a missing path, so a single miss tanked the whole task mid-drain.

This PR teaches the lint task that the BES File proto carries the workspace-relative materialization path in path_prefix + [name] alongside the canonical content URI. Bazel writes downloaded outputs to that workspace path on every host, and the lint task's own --remote_download_regex='.*AspectRulesLint.*' flag guarantees lint outputs are downloaded — so on GHA / GitLab / CircleCI / local dev that path resolves correctly to bazel-out/<config>/bin/<pkg>/<target>.AspectRulesLint<Tool>.<ext>.

New resolve_bes_file_path(ctx, file) tries the path_prefix materialization first, falls back to the existing bytestream → bb_clientd resolution for Workflows runners where outputs may stay remote-only, and returns "" when neither resolves. Callers skip empty results, increment an unreadable_outputs counter, and the post-drain code emits one summary warn() so the user knows results may be incomplete instead of a crash mid-stream.

The BES loop is also narrowed to the four rules_lint extensions (.report, .patch, .exit_code, .out) before any read, so non-lint outputs in named_set_of_files don't inflate the unreadable counter or cost a needless fs.exists call.


Changes are visible to end-users: yes

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

Suggested release notes:

  • Fixed aspect lint crashing with No such file or directory (os error 2) on non-Aspect-Workflows CI hosts (GHA, GitLab, CircleCI). The lint task now resolves BES file outputs via the workspace-relative materialization path (bazel-out/...) before falling back to bb_clientd, and gracefully skips + summarises any output that still can't be read locally.

Test plan

  • Covered by existing test cases — test-lint-template-snapshots, test-lint-annotation-plan, test-lint-linter-rows, and test-lint-detect-tool all still pass; the resolution helper is pure data flow into the existing readers and doesn't change the data shape.
  • Manual testing:
    • On a project without rules_lint set up (cd bazel-examples && aspect lint --aspect=//tools/lint:linters.bzl%shellcheck -- //...) — the run still exits cleanly with the zero-aspect ERROR line; the new resolver doesn't regress the no-output path.
    • On the failing GHA scenario — aspect lint no longer crashes; lint outputs are read from bazel-out/... (Bazel downloads them per --remote_download_regex), and any output that still can't be resolved is counted into a single trailing WARNING: lint: N BES output(s) were unavailable locally and skipped; lint results may be incomplete. line.

@aspect-workflows
Copy link
Copy Markdown

aspect-workflows Bot commented May 25, 2026

✨ Aspect Workflows Tasks

📅 Mon May 25 19:10:55 UTC 2026

⚠️ 2 flagged tasks

  • ⚠️ delivery (delivery-gha) · ⏱ 27.8s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Delivery complete (2 warn · 1 skipped)
  • ⚠️ delivery (delivery-gha-debug) · ⏱ 44.5s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Delivery complete (2 warn · 1 skipped)

✅ 17 successful tasks

  • ✅ build (build-gha) · ⏱ 32.1s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (158 built)
  • ✅ build (build-gha-debug) · ⏱ 2m 58s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (158 built)
  • ✅ buildifier (buildifier-gha) · ⏱ 11.6s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ buildifier (buildifier-gha-debug) · ⏱ 21.3s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ buildifier (buildifier-gha-ephemeral) · ⏱ 54.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format (format-gha) · ⏱ 17.9s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format (format-gha-debug) · ⏱ 33s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format (format-gha-ephemeral) · ⏱ 1m 57s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ gazelle (gazelle-from-source-gha) · ⏱ 19.3s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle (gazelle-from-source-gha-debug) · ⏱ 47.6s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle (gazelle-gha) · ⏱ 10.5s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle (gazelle-gha-debug) · ⏱ 20.8s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle (gazelle-gha-ephemeral) · ⏱ 57.3s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ lint (lint-gha) · ⏱ 16.2s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Lint complete (clean)
  • ✅ lint (lint-gha-debug) · ⏱ 22.7s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Lint complete (clean)
  • ✅ test (test-gha) · ⏱ 30.1s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (25/25 passed · 25 cached)
  • ✅ test (test-gha-debug) · ⏱ 2m 56s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (25/25 passed · 25 cached)

🔁 Reproduce

⚠️ delivery (delivery-gha · delivery-gha-debug)

aspect delivery --mode=always --track-state=false --dry-run=true

CI ran --mode=selective; --mode=always is the off-runner equivalent (selective change detection needs the Aspect Workflows delivery state backend).


⏱ Last updated Mon May 25 19:19:19 UTC 2026 · 📊 GitHub API quota 2,157/15,000 (14% used, resets in 5m)
🚀 Powered by Aspect CLI (v0.0.0-dev)  |  Aspect Build · X · LinkedIn · YouTube

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fc8bcb6e5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

"""
if file.path_prefix:
rel = "/".join(list(file.path_prefix) + [file.name])
local = ctx.std.env.current_dir() + "/" + rel
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve BES outputs from bazel_root_dir

resolve_bes_file_path builds local from current_dir, but BES file.path_prefix is rooted at the Bazel workspace/output tree, not the caller’s working directory. When aspect lint is run from a subdirectory (or when Aspect root differs from Bazel root), this computes a non-existent path, so .report/.patch/.exit_code files are treated as unreadable and skipped; that can drop diagnostics/patches and leave linter_exit_code at 0 even when a linter failed. Use ctx.std.env.bazel_root_dir() (or equivalent workspace root) for this join.

Useful? React with 👍 / 👎.

@gregmagolan gregmagolan marked this pull request as draft May 25, 2026 11:36
gregmagolan and others added 2 commits May 25, 2026 12:05
The BES `named_set_of_files` stream emits `bytestream://` URIs for
build outputs. `file_uri_to_path` resolves these to the bb_clientd
FUSE mount under `_BB_CLIENTD_ROOT`, which only exists on Aspect
Workflows runners. On any other CI host (e.g. GitHub Actions in
bazel-examples) the resolved path doesn't exist locally, so the
first `ctx.std.fs.read_to_string(filepath)` inside the drain loop
crashes the entire `lint` task with `os error 2`.

Narrow the loop to the four rules_lint extensions before any read,
then check `fs.exists(filepath)`; on miss, increment a counter and
continue. After the drain, emit one `warn()` summarising the skip
count so users know results may be incomplete. The lint aspect's
tool name still lands in `lint_tools_run` (extracted from
`file.name`, not from the unreadable file content) so the
zero-aspect safeguard isn't falsely tripped.

Repros: https://github.com/aspect-build/bazel-examples/actions/runs/26376140872/job/77636608034

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The BES `File` proto carries the workspace-relative materialization
path in `path_prefix + [name]` alongside the canonical content URI
in `file`. Bazel writes downloaded outputs to that workspace path on
every host (and `--remote_download_regex='.*AspectRulesLint.*'`
ensures lint outputs are downloaded), so it's the right local path
regardless of whether bb_clientd is mounted.

Add `resolve_bes_file_path(ctx, file)` that tries the
workspace-relative path first and falls back to the existing
bytestream → bb_clientd resolution. Only when neither exists do we
hit the skip-and-warn safety net added in the previous commit.

This restores lint findings on non-Workflows CI hosts (GHA, GitLab,
etc.) where BES emits `bytestream://` for downloaded outputs and
bb_clientd doesn't exist — previously we'd skip those, now we read
them from `bazel-out/<config>/bin/<pkg>/<target>.AspectRulesLint<Tool>.<ext>`
directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gregmagolan gregmagolan force-pushed the fix-lint-skip-unreadable-bes-outputs branch from 4fc8bcb to 8e7ce50 Compare May 25, 2026 19:05
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