Skip to content

fix(cli): IDEs with plugins using BSP can now use aspect cli instead of vanilla bazel - #1370

Closed
mcook42 wants to merge 10 commits into
mainfrom
fix/ide-bsp-sync
Closed

fix(cli): IDEs with plugins using BSP can now use aspect cli instead of vanilla bazel#1370
mcook42 wants to merge 10 commits into
mainfrom
fix/ide-bsp-sync

Conversation

@mcook42

@mcook42 mcook42 commented Aug 4, 2026

Copy link
Copy Markdown
Member

The JetBrains Bazel plugin (bazelbsp:3.2.0) drives IDE sync by shelling out to bazel on PATH. In a repo with this project's tools/bazel hook, that hook routes build/test to aspect and rewrites every Bazel-native flag to --bazel-flag=…. Three independent defects in that path caused an IntelliJ sync to fail outright, so the only way to use the plugin was to set ASPECT_WRAPPER_SKIP=1 and give up aspect's BES streaming, remote config, and task reporting entirely. See the Build Server Protocol (BSP) used by the plugin, if curious.

Each fix is a separate commit.

A caller-forwarded --target_pattern_file conflicted with the default target pattern. run_bazel_task only suppressed its own setting, but Bazel's --bazel-flag=--target_pattern_file=<path> was invisible to the runner. As a result, ctx.args.targets defaulted, causing Bazel to reject the invocation with an error. Both sync passes failed, with no last-wins rule, so the runner can only decide to invent a pattern, while precedence rules are Bazel's responsibility.

2. A caller's --build_event_binary_file was silently never written

Build::spawn appends the CLI's own --build_event_binary_file after every user flag. Bazel's option is single-valued, so last-wins meant that a caller who asked for a BEP file got a path that had been created but never written to. The plugin parses sync results from that file, so even with (1) fixed, it would have read an empty file.

Fixed by collecting a file sink for the caller's path in collect_bes_sinks, rather than stripping or reordering flags. The CLI's path still wins on the command line; the caller's file is recreated from Bazel's own byte stream, since file sinks share the BES reader's raw-bytes path.

3. bazel config was misrouted to aspect

$ bazel config --dump_all --output=json
error: unrecognized subcommand 'config'

BAZEL_VERBS in tools/bazel was generated from bazel help's "Available commands" list, which hides config. A verb missing from that table is treated as a custom aspect task and routed to aspect. The plugin calls bazel config --dump_all --output=json during sync and parses stdout as JSON, so this failed the sync on its own.

The authoritative list is bazel help completion's BAZEL_COMMAND_LIST, which includes hidden commands — verified against Bazel 9.0.1, config is the sole difference. The regeneration recipe and why it must not be bazel help are now recorded in both the script comment and tools/bazel.md:

bazel help completion | sed -n 's/^BAZEL_COMMAND_LIST="\(.*\)"$/\1/p'

The gap also silently degraded the pre-verb disambiguation walk, since KNOWN_VERBS_STR is built from the same table.

Also ignores .idea/ and .bazelbsp/, both of which are generated by the plugin.


Changes are visible to end-users: yes

  • Searched for relevant documentation and updated as needed: yes — tools/bazel.md documents BAZEL_VERBS and now carries the regeneration source and rationale
  • Breaking change (forces users to change their own code or config): no — each change either honors a flag that was previously ignored or routes a verb that previously errored
  • Suggested release notes appear below: yes

The Aspect CLI now serves as the bazel binary for IDE and BSP tooling, such as the IntelliJ Bazel plugin. A caller-forwarded --target_pattern_file no longer collides with the CLI's default target pattern; a caller's --build_event_binary_file is now written instead of being silently clobbered by the CLI's own; and bazel config reaches Bazel through the tools/bazel wrapper rather than erroring as an unknown aspect subcommand. An IntelliJ sync no longer needs ASPECT_WRAPPER_SKIP=1, so IDE-driven builds keep BES streaming, remote config, and task reporting.

Test plan

  • New test cases added — target_patterns in bazel_runner_test.axl (default forwarded when nothing supplies patterns; suppressed by a forwarded flag; suppressed by aspect's own arg; explicit patterns always forwarded; the lookup is command-scoped). The =-form / two-token / last-wins matching is deliberately not re-asserted against a faked rc — that behavior is flag_value_list's and is covered in crates/bazelrc/src/lib.rs (flag_value_eq_form_last_wins, flag_value_two_token_form). Plus bazel_bep_file resolution and the collect_bes_sinks file-sink append in bazel/build_events_test.axl
  • Covered by existing test cases — aspect tests axl (910 cases) and the full aspect test suite (26/26) both pass
  • New CI coverage — the existing test-flags-task step in both .buildkite/pipeline.yaml and .github/workflows/ci-workflows.yaml now runs a single aspect build in the plugin's exact shape and asserts a non-empty BEP at the caller's path. The unit tests cover the two resolvers in isolation; this asserts that the composed command line is accepted by Bazel and that the file lands on disk.
  • Manual testing; please provide instructions so we can reproduce:

The failure mode was captured on main first, so each result below is a before/after rather than an assertion that nothing broke:

Check Before (on main) After
Plugin-shaped invocation cannot both be specified, exit 2 exit 0, Found 1 target from the file
Caller's BEP file never created non-empty (~48 KB locally), contains the target label
WARNING: BES was not properly closed present gone
Two-token --target_pattern_file <path> cannot both be specified exit 0, BEP non-empty (~50 KB)
Pattern file from a .bazelrc line default pattern collided exit 0 — now suppresses the default too
aspect's --target-pattern-file + a forwarded one base-flag position: forwarded won aspect's arg wins (Bazel last-wins, verified by which target built)
bazel config --dump_all --output=json unrecognized subcommand 'config', exit 2 clean JSON on stdout, exit 0
BAZEL_VERBS vs BAZEL_COMMAND_LIST differs by config identical
Default target pattern (no flag forwarded) -- //... -//exclude/... unchanged
Explicit pattern + forwarded file Bazel's collision error unchanged — still Bazel's own error, exit 2
# Build and put the binary on a stable path (see the note below on why the copy matters).
ASPECT_WRAPPER_SKIP=1 bazel build //:cli
mkdir -p target/ci && cp -f "$(ASPECT_WRAPPER_SKIP=1 bazel info bazel-bin)/crates/aspect-cli/aspect-cli" target/ci/aspect-cli

# Unit tests (these are `group = ["dev"]` tasks, not bazel test targets).
aspect dev test-bazel-runner
aspect dev test-bes-sinks
aspect tests axl

# Replay the plugin's invocation shape.
PATTERNS=$(mktemp); echo "//examples/test_states:always_pass" > "$PATTERNS"
BEP=$(mktemp); rm -f "$BEP"
aspect build \
  --bazel-flag=--target_pattern_file="$PATTERNS" \
  --bazel-flag=--build_event_binary_file="$BEP" \
  --bazel-flag=--build_event_binary_file_upload_mode=wait_for_upload_complete \
  --bazel-flag=--tool_tag=bazelbsp:3.2.0
test -s "$BEP" && grep -qa always_pass "$BEP" && echo "caller's BEP written with real events"

# A pattern file from an rc file (or a --config expansion) suppresses the default too,
# which the earlier ctx.args.bazel_flags scan could not see.
RC=$(mktemp); echo "build --target_pattern_file=$PATTERNS" > "$RC"
aspect build --bazel-startup-flag=--bazelrc="$RC"

# Both spellings at once: aspect's own arg is appended last, so Bazel's last-wins picks it.
OTHER=$(mktemp); echo "//examples/test_states:always_fail" > "$OTHER"
aspect build --target-pattern-file="$PATTERNS" --bazel-flag=--target_pattern_file="$OTHER"  # builds always_pass

# Verb routing. Note 2>/dev/null, not 2>&1 — Bazel's "INFO: Invocation ID" goes to
# stderr and would corrupt the parse. The plugin reads stdout only.
comm -3 \
  <(ASPECT_WRAPPER_SKIP=1 bazel help completion | sed -n 's/^BAZEL_COMMAND_LIST="\(.*\)"$/\1/p' | tr ' ' '\n' | sed '/^$/d' | sort -u) \
  <(sed -n '/^BAZEL_VERBS=(/,/^)/p' tools/bazel | sed '1d;$d' | tr ' ' '\n' | sed '/^$/d' | sort -u)
bazel config --dump_all --output=json 2>/dev/null | python3 -c 'import json,sys; json.load(sys.stdin); print("valid JSON")'

End-to-end in IntelliJ IDEA 2026.2 with the Bazel plugin

Both the plugin's build and Sync Project actions complete against this branch. The build was confirmed from aspect's own BEP for the invocation (/tmp/<uuid>.bep.binpb), which carries the plugin's full fingerprint:

  • tool tag bazelbsp:3.2.0
  • --target_pattern_file=…/IntelliJIdea2026.2/tmp/targets-8273310712140375819
  • --build_event_binary_file=/tmp/bazel-bep-output9212484283891080652.tmp alongside --build_event_binary_file_upload_mode=wait_for_upload_complete
  • targets resolved from that file//examples/deliverable:py_deliverable, :py_deliverable2
  • outcome SUCCESS, with no cannot both be specified and no unrecognized subcommand

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of caller-provided Bazel Build Event Protocol (BEP) output files.
    • Preserved BEP file output alongside other configured event sinks.
    • Improved forwarding and resolution of target patterns, including target-pattern files.
    • Corrected routing for the Bazel config command.
  • Documentation

    • Expanded guidance for IDE/BSP build flag forwarding and BEP output.
    • Updated Bazel command-list customization instructions.
  • Chores

    • Ignored IDE-generated project and Bazel plugin state files.

mcook42 added 5 commits August 4, 2026 13:37
The runner only suppressed its default target pattern for aspect's own
`--target-pattern-file` arg. Bazel's own spelling arriving as
`--bazel-flag=--target_pattern_file=<path>` was invisible to it, so
`ctx.args.targets` fell back to its default and Bazel rejected the
invocation outright:

    ERROR: Command-line target pattern and --target_pattern_file
           cannot both be specified

That is exactly the shape the IntelliJ Bazel plugin produces: the
`tools/bazel` wrapper rewrites every Bazel-native flag to
`--bazel-flag=`, so an IDE sync never reaches Bazel.

Suppress the default when the caller forwarded the flag and gave no
explicit patterns. Only the *default* is suppressed — explicit patterns
still reach Bazel alongside the flag so Bazel emits the error above
itself, rather than this runner growing a second spelling of it.

The new `forwarded_flag_value` helper reads `ctx.args.bazel_flags` rather
than the parsed rc because target patterns are resolved before
`parse_rc` runs: the resolved pattern file has to be in the `base_flags`
the rc is built from. `--target_pattern_file` is per-invocation and not
rc material, so nothing is lost.
`Build::spawn` appends the CLI's own `--build_event_binary_file` after
every user flag. Bazel's option is single-valued, so last-wins meant a
caller who asked for a BEP file silently got nothing — the file was
created by whoever made the temp path and never written to.

The IntelliJ Bazel plugin drives sync on exactly that flag, so even with
the target-pattern collision fixed it would parse an empty file.

Collect a file sink for the caller's path instead of stripping or
reordering flags. The CLI's path still wins on the command line; the
caller's file is re-created from Bazel's own byte stream, since file
sinks share the BES reader's raw-bytes path. Placed in
`collect_bes_sinks` so all eight bazel-spawning tasks are covered — every
one of them hit the same clobbering.

Two consequences: `--build_event_binary_file_upload_mode` no longer
governs that file (the caller's existing `sink.wait()` completes it,
before the task concludes), and `--build_event_json_file` /
`--build_event_text_file` are untouched — different flags, so Bazel still
writes those itself.
`BAZEL_VERBS` was generated from `bazel help`'s "Available commands"
list, which hides `config`. A verb missing from that table is treated as
a custom aspect task, so `bazel config` became `aspect config` →
`error: unrecognized subcommand 'config'`.

The IntelliJ Bazel plugin calls `bazel config --dump_all --output=json`
during sync and parses stdout as JSON, so this failed the sync outright.

Regenerate from `bazel help completion`'s `BAZEL_COMMAND_LIST`, which
includes hidden commands — verified against Bazel 9.0.1, `config` is the
sole difference. Record the regeneration recipe and *why* it must not be
`bazel help` in the comment, and in tools/bazel.md.

The gap also silently degraded the pre-verb disambiguation walk, since
KNOWN_VERBS_STR is built from the same table.
The unit tests cover the two resolvers, but nothing exercised the pair
end to end: that the composed bazel command line is one Bazel accepts,
and that the caller's BEP file actually lands on disk.

Extend the existing `test-flags-task` step in both pipelines — it already
writes the pattern file — with one `aspect build` in the shape the
IntelliJ Bazel plugin produces (Bazel's own flag spellings behind
`--bazel-flag`), asserting a non-empty BEP at the caller's path.
Both are generated: .idea/ by the IDE, .bazelbsp/ by the JetBrains Bazel
plugin (its injected aspect .bzl files), and both showed as untracked
after an IntelliJ sync.
@aspect-workflows

aspect-workflows Bot commented Aug 4, 2026

Copy link
Copy Markdown

✨ Aspect Workflows Tasks

📅 Wed Aug 12 19:20:44 UTC 2026

❌ 1 failed task

  • ❌ delivery-uncacheable [delivery] · ⏱ 25.3s · ✨ Aspect · 🐙 GitHub Actions
    💬 failed in deliver · Delivery failed (1 delivery fail)

⚠️ 3 flagged tasks

  • ⚠️ delivery-gha-debug [delivery] · ⏱ 40.1s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Delivery complete (1 delivered · 2 warn · 4 skipped)
  • ⚠️ delivery-gha [delivery] · ⏱ 45.3s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Delivery complete (1 delivered · 2 warn · 4 skipped)
  • ⚠️ delivery-uncacheable-warn [delivery] · ⏱ 14.7s · ✨ Aspect · 🐙 GitHub Actions
    💬 Delivery complete (1 warn)

✅ 29 successful tasks

  • ✅ axl-smoke-gha-bootstrap [build] · ⏱ 2m 5s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (1 built)
  • ✅ run-axl-smoke [run] · ⏱ 19.1s · 🐙 GitHub Actions · ☑️ Check
    💬 Ran //examples/deliverable:py_deliverable
  • ✅ run-axl-smoke-2 [run] · ⏱ 14.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Ran //examples/deliverable:sh_deliverable
  • ✅ axl-tests-gha-bootstrap [build] · ⏱ 17.4s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (1 built)
  • ✅ build-gha-debug [build] · ⏱ 22m 46s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (172 built)
  • ✅ build-gha [build] · ⏱ 10m 4s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (172 built)
  • ✅ build-gha-ephemeral [build] · ⏱ 39.9s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (10 built)
  • ✅ buildifier-gha-debug [buildifier] · ⏱ 45.3s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ buildifier-gha [buildifier] · ⏱ 39.1s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ delivery-no-remote-exec [delivery] · ⏱ 11.7s · ✨ Aspect · 🐙 GitHub Actions
    💬 Delivery complete (no deliveries)
  • ✅ format-gha-debug [format] · ⏱ 1m 9s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task [format] · ⏱ 1m 38s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task-2 [format] · ⏱ 10.9s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task-3 [format] · ⏱ 11.1s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task-4 [format] · ⏱ 11.3s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-gha [format] · ⏱ 1m 41s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ gazelle-gha-debug [gazelle] · ⏱ 52s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle-from-source-gha-debug [gazelle] · ⏱ 2m 9s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle-from-source-gha [gazelle] · ⏱ 2m 9s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle-gha [gazelle] · ⏱ 37.4s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ init-shell [build] · ⏱ 33.1s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (10 built)
  • ✅ lint-gha-debug [lint] · ⏱ 51.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Lint complete (clean)
  • ✅ lint-gha [lint] · ⏱ 47.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Lint complete (clean)
  • ✅ test-gha-debug [test] · ⏱ 18m 12s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (26/26 passed · 20 cached)
  • ✅ test-gha-ide-passthrough [build] · ⏱ 13.3s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (1 built)
  • ✅ test-gha-coverage [test] · ⏱ 1m 11s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-gha-target-pattern-file [test] · ⏱ 21.2s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-gha [test] · ⏱ 10m 55s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (26/26 passed · 20 cached)
  • ✅ test-gha-ephemeral [test] · ⏱ 53.8s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)

🔁 Reproduce

❌ delivery (delivery-uncacheable · delivery-gha-debug · delivery-gha · delivery-uncacheable-warn)

# --mode=always --track-state=false for off-runner with no state backend.
aspect delivery \
  --commit-sha=483a00be7b254b15f7378ea9297bfc8b9ddeebf0 \
  --mode=always \
  --track-state=false \
  --dry-run=true

Install aspect: aspect.build/docs/cli/install


⏱ Last updated Wed Aug 12 19:56:27 UTC 2026 · 📊 GitHub API quota 1,521/15,000 (10% used, resets in 17m)
🚀 Powered by Aspect CLI (v0.0.0-dev)  |  Aspect Build · X · LinkedIn · YouTube

@mcook42
mcook42 requested review from jbedard and thesayyn August 4, 2026 21:07
@mcook42
mcook42 marked this pull request as ready for review August 4, 2026 21:07
@mcook42 mcook42 changed the title Fix/ide bsp sync fix(cli): ide's with plugins using BSP can now use aspect cli instead of vanilla bazel Aug 4, 2026
"""
eq_prefix = name + "="
value = ""
for i, flag in enumerate(bazel_flags):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this sounds really unprincipled/workaroubd. can we understand what's really wrong and fix it?

I'd rather let bazel decide what's last to win rather than us deciding it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It was a workaround, and I should have caught this before posting, so thanks for holding me accountable.

The runner already avoided sending a target pattern when Aspect's own --target-pattern-file flag was set. The bug is that it only checked that one spelling. When the pattern file arrives as a forwarded --bazel-flag=--target_pattern_file=<path>, as an IntelliJ sync sends, the runner never sees it and falls back to its default pattern of .... Bazel then gets both that pattern and the pattern file and refuses to run, since it has no last-wins rule for that pair.

The fix is to check the flags Bazel will actually run with, rather than a specific arg. We now ask the parsed command with rc.flag_value("--target_pattern_file", ...), which detects the flag regardless of how it arrived, and skips sending a pattern if it is set. If the user typed patterns, we always send them and let Bazel report the conflict, so our own error in that case is removed.

Getting to that point took a reordering. The parsed command did not yet exist when patterns were being decided, because we were adding Aspect's own pattern file flag to the same list of flags that gets parsed. Moving that flag out of that list let target resolution happen after the parse, which is what makes rc.flag_value available to ask.

@jbedard

jbedard commented Aug 4, 2026

Copy link
Copy Markdown
Member

This seems like 3 separate issues that can have independent fixes?

@mcook42

mcook42 commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

This seems like 3 separate issues that can have independent fixes?

You're right that these are three separate issues, and I could split them into three separate PRs. However, until all three of these issues are fixed, the IntelliJ/JetBrains Bazel IDE plugin won't work. The three fixes in this PR need to land before the plugin works with aspect CLI, so in my mind it was best to land them all in a single review.

I'm happy to close this PR and open three independent PRs for smaller reviews if that's preferred.

@mcook42 mcook42 changed the title fix(cli): ide's with plugins using BSP can now use aspect cli instead of vanilla bazel fix(cli): IDEs with plugins using BSP can now use aspect cli instead of vanilla bazel Aug 4, 2026
@mcook42
mcook42 marked this pull request as draft August 5, 2026 14:17
`run_bazel_task` only suppressed its target-pattern default for aspect's own
`--target-pattern-file` arg. Bazel's spelling reaching aspect as
`--bazel-flag=--target_pattern_file=<path>` — what `tools/bazel` produces for
every Bazel-native flag, and so what an IntelliJ BSP sync sends — lands in
`ctx.args.bazel_flags` instead, invisible there. `ctx.args.targets` then fell
back to its declared default `["..."]`, which reaches Bazel as residue
alongside the forwarded flag:

    ERROR: Command-line target pattern and --target_pattern_file
           cannot both be specified

That pair has no last-wins rule in Bazel — it is a hard conflict. So the only
decision this runner owns is whether to invent a pattern the user never typed;
every precedence question belongs to Bazel's own option parser.

A `RunCommand` is the *effective* option set, not just the rc file — CLI flags
live in it as the `<command line>` source — so
`rc.flag_value("--target_pattern_file", ...)` answers for the forwarded
spelling with the same last-wins / `=`-form / two-token matching as
`crates/bazelrc`. Reaching it only needed the ordering fixed: aspect's own
`--target-pattern-file` moves from `base_flags` (an *input* to `parse_rc`) to
the per-invocation `flags`, freeing patterns to resolve after the rc parse.
That also keeps the flag out of the rc's `always` bucket, so it is no longer
expanded for every command.

Explicit patterns are always forwarded, so Bazel emits the error above itself
and the runner's second spelling of it is dropped. The existence check on
`--target-pattern-file` stays, since aspect resolves that path.

Supersedes the reverted `forwarded_flag_value` scan (c993267 / 2af86d1),
which reimplemented Bazel's option parsing in AXL.
@mcook42
mcook42 marked this pull request as ready for review August 5, 2026 17:04
@mcook42
mcook42 requested a review from thesayyn August 5, 2026 17:04
…e-bsp-sync

# Conflicts:
#	crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner.axl
#	crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner_test.axl
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2db4b57f-676b-47cd-a165-6642e8351f0a

📥 Commits

Reviewing files that changed from the base of the PR and between 1c4650a and 483a00b.

📒 Files selected for processing (2)
  • crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner.axl
  • crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner_test.axl
💤 Files with no reviewable changes (1)
  • crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner.axl
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner_test.axl

📝 Walkthrough

Walkthrough

The CLI resolves target-pattern files after rc parsing and preserves caller-provided binary BEP output through a CLI-managed sink. Buildkite and CI smoke tests cover IDE/BSP flag forwarding. The Bazel wrapper recognizes config, and IDE-generated files are ignored.

Changes

IDE/BSP Bazel forwarding

Layer / File(s) Summary
Target-pattern resolution
crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner.axl, crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner_test.axl, .aspect/config.axl
The runner resolves explicit targets and suppresses default targets when Aspect or Bazel target-pattern files are active after rc parsing. Tests cover defaults, suppression, explicit targets, and command-scoped lookups.
Caller BEP file sink
crates/aspect-cli/src/builtins/aspect/bazel/build_events.axl, crates/aspect-cli/src/builtins/aspect/bazel/build_events_test.axl
The build-events collector resolves --build_event_binary_file, appends a CLI-managed file sink, and finalizes it independently of Bazel upload-mode handling. Tests cover sink ordering and duplicate gRPC suppression.
IDE/BSP smoke coverage
.buildkite/pipeline.yaml, .github/workflows/ci-workflows.yaml
Smoke tests forward target-pattern, BEP, upload-mode, and tool-tag flags. They verify the caller-provided BEP file and remove temporary files.

CLI tooling maintenance

Layer / File(s) Summary
Bazel command routing
tools/bazel, tools/bazel.md
The wrapper adds config to BAZEL_VERBS. Documentation describes regeneration from bazel help completion.
IDE workspace ignores
.gitignore
The repository ignores .idea/ and .bazelbsp/ files.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant IDEBSP
  participant AspectCLI
  participant Bazel
  participant BEPFile
  IDEBSP->>AspectCLI: invoke build with forwarded flags
  AspectCLI->>Bazel: run with target-pattern and BEP settings
  AspectCLI->>BEPFile: write caller-provided binary BEP output
  AspectCLI-->>IDEBSP: complete build and cleanup
Loading

Suggested reviewers: jbedard, thesayyn

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: enabling IDEs with BSP plugins to use Aspect CLI instead of vanilla Bazel.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ide-bsp-sync

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/bazel.md`:
- Around line 156-158: Update the code fence surrounding the bazel help
completion command in tools/bazel.md to specify the shell language as sh,
resolving the markdownlint MD040 warning while leaving the command unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d1818711-7ed0-47b1-bd17-1357abd11e3a

📥 Commits

Reviewing files that changed from the base of the PR and between 1119bfb and 1c4650a.

📒 Files selected for processing (10)
  • .aspect/config.axl
  • .buildkite/pipeline.yaml
  • .github/workflows/ci-workflows.yaml
  • .gitignore
  • crates/aspect-cli/src/builtins/aspect/bazel/build_events.axl
  • crates/aspect-cli/src/builtins/aspect/bazel/build_events_test.axl
  • crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner.axl
  • crates/aspect-cli/src/builtins/aspect/private/lib/bazel_runner_test.axl
  • tools/bazel
  • tools/bazel.md

Comment thread tools/bazel.md
@mcook42
mcook42 marked this pull request as draft August 12, 2026 17:26
@mcook42
mcook42 marked this pull request as ready for review August 12, 2026 21:18
@jbedard

jbedard commented Aug 13, 2026

Copy link
Copy Markdown
Member

I still keep getting confused when trying to review this. Would it be easy to split parts of this into separate PRs? Things like the sink fix, targets files etc.

If I see something easy to understand I'm significantly more likely understand+approve before getting pulled into something else and forgetting all the context here...

@mcook42

mcook42 commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Superseded — split into three separate PRs per review feedback, one per fix:

  1. fix(cli): honor a caller-forwarded --target_pattern_file #1388--target_pattern_file conflict
  2. fix(bes): write the caller's --build_event_binary_file #1389--build_event_binary_file silently clobbered
  3. fix(tools): route bazel config to vanilla bazel #1390bazel config misrouted to aspect

Closing this in favor of those.

@mcook42 mcook42 closed this Aug 13, 2026
mcook42 added a commit that referenced this pull request Aug 13, 2026
The unit tests cover collect_bes_sinks in isolation, but nothing
exercised it end to end: that a caller's --build_event_binary_file,
forwarded in the shape the IntelliJ Bazel plugin produces (Bazel's own
flag spelling behind --bazel-flag), actually lands on disk.

Extend the existing test-flags-task step in both pipelines with one
aspect build in that shape, asserting a non-empty BEP at the caller's
path.

Split out of the combined IDE/BSP smoke-test commit in #1370 — the
--target_pattern_file half goes with that fix's own PR.
mcook42 added a commit that referenced this pull request Aug 13, 2026
The unit tests cover target_patterns in isolation, but nothing exercised
it end to end: that a caller-forwarded --target_pattern_file, in the shape
the IntelliJ Bazel plugin produces (Bazel's own flag spelling behind
--bazel-flag), is a command line Bazel actually accepts.

Extend the existing test-flags-task step in both pipelines with one
aspect build in that shape.

Split out of the combined IDE/BSP smoke-test commit in #1370 — the
--build_event_binary_file half goes with that fix's own PR.
mcook42 added a commit that referenced this pull request Aug 13, 2026
The unit tests cover target_patterns in isolation, but nothing exercised
it end to end: that a caller-forwarded --target_pattern_file, in the shape
the IntelliJ Bazel plugin produces (Bazel's own flag spelling behind
--bazel-flag), is a command line Bazel actually accepts.

Extend the existing test-flags-task step in both pipelines with one
aspect build in that shape.

Split out of the combined IDE/BSP smoke-test commit in #1370 — the
--build_event_binary_file half goes with that fix's own PR.
mcook42 added a commit that referenced this pull request Aug 13, 2026
The unit tests cover collect_bes_sinks in isolation, but nothing
exercised it end to end: that a caller's --build_event_binary_file,
forwarded in the shape the IntelliJ Bazel plugin produces (Bazel's own
flag spelling behind --bazel-flag), actually lands on disk.

Extend the existing test-flags-task step in both pipelines with one
aspect build in that shape, asserting a non-empty BEP at the caller's
path.

Split out of the combined IDE/BSP smoke-test commit in #1370 — the
--target_pattern_file half goes with that fix's own PR.
mcook42 added a commit that referenced this pull request Aug 14, 2026
Split out of #1370 (fix 3 of 3) . See that PR's description for full
context on all three defects found in the same investigation.

```
$ bazel config --dump_all --output=json
error: unrecognized subcommand 'config'
```

`BAZEL_VERBS` in `tools/bazel` was generated from `bazel help`'s
"Available commands" list, which *hides* `config`. A verb missing from
that table is treated as a custom aspect task and routed to `aspect`.
The IntelliJ Bazel plugin calls `bazel config --dump_all --output=json`
during sync and parses stdout as JSON, so this failed the sync on its
own.

The authoritative list is `bazel help completion`'s
`BAZEL_COMMAND_LIST`, which includes hidden commands — verified against
Bazel 9.0.1, `config` is the sole difference. The regeneration recipe
and *why it must not be `bazel help`* are now recorded in both the
script comment and `tools/bazel.md`:

```
bazel help completion | sed -n 's/^BAZEL_COMMAND_LIST="\(.*\)"$/\1/p'
```

The gap also silently degraded the pre-verb disambiguation walk, since
`KNOWN_VERBS_STR` is built from the same table.

Also ignores `.idea/` and `.bazelbsp/`, both of which are generated by
the plugin.

### Test plan

- Manual verification on this branch: `tools/bazel config --dump_all
--output=json` now reaches vanilla Bazel's own `config` subcommand
(Bazel's own "No configurations found" message) instead of the aspect
wrapper's `unrecognized subcommand 'config'`.
mcook42 added a commit that referenced this pull request Aug 14, 2026
Split out of #1370 (fix 1 of 3). See that PR's description for full
context on all three defects found in the same investigation.

A caller-forwarded `--target_pattern_file` conflicted with the default
target pattern. `run_bazel_task` only suppressed its own
`--target-pattern-file` setting, but Bazel's
`--bazel-flag=--target_pattern_file=<path>` — what `tools/bazel`
produces for every Bazel-native flag, and so what an IntelliJ BSP sync
sends — was invisible to the runner. `ctx.args.targets` then fell back
to its declared default (`["..."]`), which reaches Bazel as residue
alongside the forwarded flag:

```
ERROR: Command-line target pattern and --target_pattern_file cannot both be specified
```

That pair has no last-wins rule in Bazel. It's a hard conflict, so the
only decision `run_bazel_task` owns is whether to invent a pattern the
user never typed. Every precedence question belongs to Bazel's own
option parser. `target_patterns()` now asks the effective `RunCommand`
(`rc.flag_value("--target_pattern_file", ...)`) whether a pattern file
reached Bazel by any spelling, using the same last-wins / `=`-form /
two-token matching as `crates/bazelrc`.

### Test plan

- New test cases — `target_patterns` in `bazel_runner_test.axl`: default
forwarded when nothing supplies patterns; suppressed by a forwarded
`--bazel-flag`; suppressed by aspect's own `--target-pattern-file`;
explicit patterns always forwarded; the lookup is command-scoped.
Verified locally: `bazel run //crates/aspect-cli:aspect-cli -- dev
test-bazel-runner` → `bazel_runner_test.axl: OK (10 sections)`.
- New CI coverage — extends `test-flags-task` in both
`.buildkite/pipeline.yaml` and `.github/workflows/ci-workflows.yaml`
with one `aspect build` in the plugin's exact shape
(`--bazel-flag=--target_pattern_file=...`), asserting the composed
command line is accepted by Bazel.
- This is the `--target_pattern_file` half of #1370's combined IDE/BSP
smoke-test commit — the `--build_event_binary_file` half went to that
fix's own PR instead, since a single combined step would make each split
PR's CI depend on the other's fix also being merged.

### Before / after (captured on `main`)

| Check | Before | After |
|---|---|---|
| Plugin-shaped invocation | `cannot both be specified`, exit 2 | exit 0
|
| Two-token `--target_pattern_file <path>` | `cannot both be specified`
| exit 0 |
| Pattern file from a `.bazelrc` line | default pattern collided | exit
0 — now suppresses the default too |
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.

3 participants