5.2.3 bazel aquery — Action Graph

When a compiler receives the wrong flag, a linker sees an unexpected library, or nobody knows which action produces a file, the dependency graph is no longer specific enough. You need the execution plan Bazel created after analysis. bazel aquery exposes that plan: actions, their input and output artifacts, mnemonics, and command lines.1

This makes aquery the answer to “what does Bazel plan to run?”, not “what already ran, and how long did it take?” The command performs analysis, but it does not execute the actions it reports.2

5.6.8 Skyscope — Skyframe Visualizer complements this command when the investigation needs a browser view of retained Skyframe relationships rather than action-plan output.

What will Bazel run — and what does that plan not prove?
aquery exposes the execution plan created by analysis. Evidence about an actual build begins beyond that plan.
TARGET
The requested label
Starting scope
//app:server
CONFIGURED TARGET
The analyzed target instance
Label plus configuration
label + configuration
The configuration resolves select() branches, transitions, and toolchain edges.
ACTIONS + ARTIFACTS
The generated execution plan
Inspect related fields together
inputs mnemonic command outputs
evidence boundary · aquery stops at the plan
PROFILE
duration and critical path
EXECUTION LOG
what ran and where
CACHE RECORD
hit or miss evidence
A wrong flag points to the command. A missing file points to inputs or outputs. ActionKey does not prove execution or a cache hit, and it is not the remote-cache lookup key.

Start with one action

The simplest invocation shows the actions generated for one configured target:

bazel aquery //examples/basic:module_report

If the result contains several actions, narrow it by mnemonic first. A mnemonic is the short name a rule assigns to an action kind, such as CppCompile, GoLink, or GlyphReport:

bazel aquery 'mnemonic("GlyphReport", //examples/basic:module_report)'

You can run this command in the mini-ruleset project. The rule that registers GlyphReport is in glyph/internal/rules.bzl, where its mnemonic is chosen as part of registering the action described in 4.2.2 Actions. This example also separates two roles: the rule author chooses the mnemonic and declares the execution contract from 4.4.3 Action Execution Contract. The consultant uses aquery to inspect that contract for a particular target.

In the default text format, an action block contains the fields that drive the investigation:

  • Mnemonic identifies the action kind.
  • Target names the configured target that generated it.
  • Configuration identifies the configuration.
  • Inputs and Outputs show artifacts on the action-graph edges.
  • Command Line shows the program and arguments Bazel plans to use.
  • ActionKey encodes significant behavior of the action kind, but must be interpreted carefully.

These fields turn a symptom into a testable hypothesis. For a wrong compiler flag, inspect Command Line. For a missing header or generated file, inspect Inputs. If all you know is the bad artifact's name, use Outputs to find its producer.

Choose the right scope

An expression containing only a label selects actions generated by that target. To inspect the complete plan needed to build it, use deps():

bazel aquery 'deps(//app:server)'

aquery works from the configured target graph after analysis, then exposes the actions generated from it. It therefore respects the active configuration like cquery, but moves down one layer—from targets and dependencies to commands and artifacts.1 If the question is “which select() branch won?”, begin with 5.2.2 bazel cquery — Configured Graph. If it is “what exact command did that choice produce?”, move to aquery.

Do not add deps() automatically. For a large target it can produce an enormous result and bury the action you need. Start with the direct target and expand the scope only when you suspect that the action comes from a dependency.

Filter actions, not targets

aquery adds three regular-expression filters:1

# Action kind
bazel aquery 'mnemonic("CppCompile", deps(//app:server))'

# Actions with an input matching the pattern
bazel aquery 'inputs(".*\\.proto", deps(//app:server))'

# Actions producing an artifact matching the pattern
bazel aquery 'outputs(".*server.*", deps(//app:server))'

Nest filters to combine conditions:

bazel aquery \
  'mnemonic("Cpp.*", inputs(".*\\.cc", deps(//app:server)))'

The nesting direction matters. deps() returns configured targets, while mnemonic(), inputs(), and outputs() turn such a set into actions. Thus mnemonic("CppCompile", deps(...)) is valid, but deps(mnemonic(...)) is not: a target function cannot consume actions.1

Build the expression from the inside out: establish the target set, such as deps(//app:server), then wrap it in action filters.

Read the command line together with its artifacts

The presence or absence of one flag is often insufficient evidence. Inspect these fields together:

  1. Program and arguments — did Bazel select the expected tool and options?
  2. Inputs — are the tool, sources, generated files, and parameter files part of the action?
  3. Outputs — which artifact can later correlate this action with an execution profile or log?
  4. Configuration and target — are you looking at the intended instance of the label?

Long commands may place arguments in a parameter file. Use --include_param_files to include its contents. The option also enables command line output automatically.1

bazel aquery --include_param_files \
  'mnemonic("CppCompile", //app:server)'

This is a good first response to “the compiler should receive this flag, but I cannot see it.” Filter by mnemonic, inspect the ordinary command line, then inspect the parameter file. Only then trace the setting back through the rule or toolchain configuration.

Do not confuse the plan with execution

aquery describes a generated action. It does not tell you whether a particular build got a cache hit, where the action ran, or how long it took. For timing, use the JSON profile. Bazel's documentation recommends correlating profile data with aquery through the action's primary output.1 5.7 Cache & Execution covers execution and cache evidence, while 5.4.2 Critical Path covers timing and the critical path.

The text output's ActionKey is especially easy to misread. It is the analysis metadata key returned for the action, encoding significant behavior of that kind of action. A material rule or command-line change, a relevant environment or configuration change, or a change to the action-kind implementation can change it. Bazel typically versions the last case with a UUID.1

That implementation logic is distinct from the executable tool artifact. A change to the contents of an input file—including a compiler or other tool listed as an input—does not have to change the ActionKey shown by aquery. Input-content digests participate later in the execution/cache key. The visible ActionKey is therefore neither a digest of all input contents nor the key used by the remote-cache client.1

In practice:

  • compare the command, environment, and input list when the action's shape has changed.
  • use execution and cache records to explain a hit or miss in a particular invocation.
  • never infer “cache hit” or “cache miss” from aquery's ActionKey alone.

Choose an output format for the consumer

The default --output=text is suited to manual inspection. --output=summary provides a compact overview, while --output=commands prints one command per line. For automation, use structured proto, textproto, or jsonproto. The enclosing message is analysis.ActionGraphContainer.1

bazel aquery --output=jsonproto \
  'mnemonic("GlyphReport", //examples/basic:module_report)' \
  > action-graph.json

Do not make a tool depend on record order. The query contract does not guarantee it.1 Identify actions by fields such as outputs, mnemonic, and target instead of an array position. For an oversized result, --noinclude_commandline and --noinclude_artifacts can omit data that is included by default.1

When the question is “what changed between two revisions?”, capture the same query and structured format on both sides, then choose an explicit matching policy. Use output paths by default only when both revisions produce comparable products. Do not treat a mnemonic as an action identity: it names an action kind and is not unique. bazel_difftools offers mnemonic matching when output paths changed, but its mnemonic map keeps only one action per mnemonic, so a later action of the same kind replaces an earlier one. That mode is a lossy heuristic, not a stable correspondence.3 A small upstream examples/simple/ fixture demonstrates this before/after workflow and the project's preview CLI and regression data document the matching behavior.3 For a comparator you need to trust, first dereference the container's dump-local IDs: they join targets, path fragments, artifacts, and configurations only within that dump.4 Compare labels, resolved execution paths, and semantic configuration identity rather than raw IDs. Decide explicitly whether a configuration checksum belongs to that identity or is itself a change the diff should expose. Then retain all duplicate candidates and use a composite key suited to the investigation, such as mnemonic plus target, configuration, and primary output. Detect and report any remaining ambiguity instead of silently discarding an action. This is a comparison tool's matching policy, not a guarantee that aquery assigns stable record identities.

extra

--skyframe_state searches the action graph retained by the current Bazel server without repeating analysis. It supports only proto and textproto, and its expression cannot contain target labels. This is useful when you know an artifact's name but no longer know which previously analyzed target produced it.1

A short investigation sequence

  1. State the observation: a wrong flag, missing input, unexpected output, or unknown producer.
  2. Begin with a specific label, without deps().
  3. Narrow the result with mnemonic(), inputs(), or outputs().
  4. Read target, configuration, command, inputs, and outputs together.
  5. If the question is about what happened, switch from the action plan to a profile, execution log, or cache evidence.
key takeaway

Use bazel aquery to inspect Bazel's post-analysis execution plan. Start from a narrow target, wrap the target expression in mnemonic(), inputs(), or outputs() filters, and interpret commands together with artifacts and configuration. The result describes planned actions. It does not prove their duration, execution location, or cache result. Its ActionKey is action-analysis metadata, not the remote-cache lookup key and not a digest of input contents.

Check your understanding · 4 questions

1.A build was unexpectedly slow, and aquery shows a CppCompile action with a stable ActionKey. What can you conclude?

Select one answer

2.A generated file has an unexpected name, and you want to find the action that plans to produce it without flooding the result. Which steps are good starting points?

Select all that apply

3.The compiler command shown by aquery appears to omit a flag that may have been placed in a parameter file. What should you try next?

Select one answer

4.Match each consumer or investigation need to the most suitable evidence or output:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
Quick human overview of action kinds
Automation that parses action graph fields
Proof of cache hits and where actions ran
0 of 4 answered

Footnotes

  1. Action Graph Query (aquery) — action-graph semantics, filters, output options, parameter files, ActionKey, Skyframe mode, and profile correlation 1 2 3 4 5 6 7 8 9 10 11 12

  2. A guide to Bazel query — practical distinction among query, cquery, and aquery by graph layer

  3. bazel_difftools — structural action-graph comparison — the upstream README defines output-path and mnemonic matching. pkg/action/output_map.go implements each as a one-action-per-key map, so duplicate mnemonics are overwritten by the last action 1 2

  4. Bazel — core implementation, documentation, and regression corpussrc/main/protobuf/analysis_v2.proto defines artifact, target, rule-class, aspect, dep-set, and configuration IDs that are valid only for one dump. Action and artifact fields reference those records and path fragments