Bazel: query vs cquery (and Why Output Paths Need cquery)

2026/02/18

BUILDbazel

Tried to find where Bazel puts the output of a target. Used query. Got a label. Used cquery. Got an actual path. What’s the difference?

query = Static

bazel query reads your BUILD files. That’s it. No build configuration, no platform resolution, no toolchain selection. It’s fast because it doesn’t actually figure out how to build anything.

bazel query '//image:app_layer' --output=build | grep "out ="
# out = "//image:app_layer.tar"

That’s a label//image:app_layer.tar. It tells you what the output is called, but not where it lives on disk.

cquery = Configured

bazel cquery does everything query does, plus it resolves the actual build configuration — your platform, compilation mode, toolchains, select() statements.

bazel cquery --output=files //image:app_layer
# bazel-out/darwin_arm64-fastbuild/bin/image/app_layer.tar

That’s an actual file path you can use. Notice darwin_arm64-fastbuild in there — that’s your platform and build mode baked into the path.

Why the Path Is Config-Dependent

Bazel can build the same target for different configurations simultaneously. The output directory encodes the config so they don’t collide:

bazel-out/darwin_arm64-fastbuild/bin/...    # debug build, macOS ARM
bazel-out/darwin_arm64-opt/bin/...          # optimized build, macOS ARM
bazel-out/k8-fastbuild/bin/...             # debug build, Linux x86

query doesn’t know which config you’re targeting. cquery does.

The Mental Model

query   = "what exists?"        (reads BUILD files)
cquery  = "what will happen?"   (resolves the full build graph)
build   = "do it"
run     = "do it, then execute"

Use query for exploration — finding targets, tracing deps, checking what’s in a package. Use cquery when you need real file paths or need to see how select() statements resolve on your machine.