6.3.2 Remote Executor Matching
Seeing a platform label in Bazel's analyzed action can prove which compatible execution platform Bazel selected. Toolchain-resolution evidence and the action's configured tool inputs establish the selected toolchain separately. Neither can prove that a remote worker with that environment exists, that a backend will route the action to it, or that the worker really provides what the label promises. Matching remote actions is the operational chain that closes those gaps.
GlyphReport begins with its rule-assigned execution group, Bazel's resolved execution platform and toolchain, and stable requirements. REAPI carries opaque properties. A chosen backend interprets exact documented values, maps them to a bounded compatible pool, and an actual executor must fulfill its OS, architecture, tool, runtime, image, and resource promise. Matching needs both uncached placement evidence and visible rejection of incompatible, unknown, or ambiguous requests.
Begin with one action class already admitted by 6.3.1 Making Actions Work Remotely. Its files and tools are declared, its remaining runtime requirements are known, and local fallback is not allowed to hide the test. The job here is to preserve that action's Bazel-side intent as it crosses the protocol and backend boundaries, then obtain evidence that the intended executor actually ran it.
Follow the contract across four boundaries
Keep four statements separate:
- Bazel intent: which execution platform and toolchain did Bazel select for this action?
- Protocol transport: which properties did Bazel place in the remote action request?
- Backend routing: how does this backend interpret those properties and restrict the request to a compatible executor pool?
- Executor promise: which concrete OS, architecture, tool/runtime version, image, and resources did the selected executor provide?
The chain is only as strong as its weakest link. If Bazel selects the right platform but the backend ignores an unknown property, intent was not preserved. If routing chooses the intended pool but the worker image is stale, placement was correct while the executor promise was false.
Bazel states intent; it does not provision a worker
Do not confuse the target platform with the execution platform. The target
platform describes what the produced artifact is for. The execution platform
describes where a build-time action and its tools run. A toolchain can therefore
target one platform while its compiler executes on another; its
target_compatible_with and exec_compatible_with constraints describe those
two directions separately.1
4.6.3 Toolchain Resolution explains how Bazel combines those constraints to select a toolchain and execution platform. At this boundary, the important result is not merely a label: it is a compatible tool, configured for the selected execution environment. A binary built for the client host may fail on a different remote execution platform even when it has the expected filename. Toolchains avoid that host assumption by selecting or supplying a suitable tool.2
One target can also create actions with different requirements. A rule assigns an action to an execution group; Bazel then resolves that group's toolchains and execution platform. Group-qualified execution properties let one action class override the target's default properties.3 That makes “the target's execution platform” too coarse for some investigations: trace the platform, toolchain, and properties of the specific action or execution group. 4.6.5 Execution Groups & Auto Exec Groups covers how rules define and assign those groups; here you consume the resolved result.
The Mini Ruleset report action
provides a concrete Bazel-side example. GlyphReport uses the glyph_report
execution group, resolves the Glyph toolchain through that group, and requires
the report_pool constraint. Its
platform declarations
give the matching execution platform an execution property.
Run the project's action query to inspect Bazel's resolved action rather than inferring it from the declarations alone:
bazel aquery 'mnemonic("GlyphReport", //examples/basic:module_report)' --output=textproto
In the result, inspect the GlyphReport action's execution platform and
effective execution properties. The checked-in
captured excerpt
shows both execution_platform: "//platforms:report_worker" and the effective
Pool=glyph-report property for this action. The runnable
Mini Ruleset workspace pins this command in its
verification metadata so the observation can be repeated for its Bazel version.
This project proves only Bazel intent. Its platforms inherit the local host so
the example remains runnable without an RBE service. It contains no remote
scheduler or executor image. The report_worker label therefore does not create
a worker, and its property value is not placement evidence.
REAPI transports properties; the backend owns their meaning
A platform's exec_properties is a string-to-string map. Before transport,
Bazel computes the effective map: a child platform overrides or removes values
inherited from its parent; target-level values override values from the selected
platform; and a group-qualified target key such as glyph_report.Pool overrides
the corresponding unprefixed key for that group. A target rejects a property
qualified with an unknown group, while a platform may carry such group-qualified
properties for actions that use the group.1,3
Bazel treats the backend meaning of the resulting names and values as opaque and
forwards the effective map through the REAPI Platform field. That is the
portable boundary: the protocol can carry named properties with a remote action.
REAPI does not standardize a universal property vocabulary, pool layout, matching algorithm, worker image runtime, or scheduler topology. To understand why a property changes worker selection, the protocol definition points you to the chosen server's scheduler and deployment contract.4
This distinction prevents a common configuration error. A key such as Pool,
an image selector, or a resource name may be meaningful to one backend and
ignored, rejected, or interpreted differently by another. Even capitalization
can be part of that backend contract. Keep vendor property names out of the
portable platform vocabulary unless the deployment is deliberately coupled to
that backend and the coupling is documented.
Concrete systems demonstrate the variety. One EngFlow embedded-testing example uses a pool property to send test actions to ARM workers.5 A BuildBuddy design organizes executors into pools and incorporates image, OS, CPU, and pool information into its routing key.6 These are useful examples of backend interpretation, not standard REAPI syntax.
Translate to a bounded fleet vocabulary
The ruleset and workload should express stable requirements; the fleet operator should own their translation into deployment inventory. Treat that translation as a versioned table rather than scattered free-form strings:
| Bazel-side intent | Backend contract | Bounded destination | Executor promise |
|---|---|---|---|
| selected execution platform and toolchain | documented property set and exact supported values | named pool or finite compatible pool set | OS/architecture plus tool and runtime/image versions |
| action- or group-specific resource need | documented resource class | pool with that class available | the requested resource can be allocated for the action |
| unsupported or unknown requirement | explicit reject behavior | no default or catch-all placement | no executor is falsely advertised as compatible |
Prefer a small vocabulary that corresponds to capacity you can inventory and test. A new property value should require an operator-owned mapping, a compatible pool, and an acceptance probe. Otherwise property cardinality grows faster than the fleet can represent, and a nominally precise request becomes ambiguous or unmatchable.
The boundary with scheduling matters here. This article establishes the finite set of destinations that may satisfy a request and requires fail-closed behavior when none does. Queue admission, priority, fairness, dispatch, backpressure, and overload policy continue in 6.3.8 Scheduling Actions on Remote Executors. Capacity sizing for those matchable pools continues in 6.3.12 Remote Execution Capacity.
The actual executor must fulfill the promise
Pool membership is still only a claim. Record the environment version that an executor actually offered: at minimum the relevant OS and architecture, the tool or runtime supplied outside declared action inputs, and the image or other environment revision that defines those surfaces. A tool being present is not enough if its dynamic libraries, SDK, runtime configuration, license access, or kernel capability are incompatible.
This is why heterogeneous workloads are a strong matching probe. A Windows RBE case needed a Windows/x86 platform, a container-image execution property, and a C++ toolchain configured for that container; those were three connected requirements, not three names for the same mechanism.7 An iOS case can start Bazel on Linux while dispatching compilation to macOS workers with Xcode, showing again that the client host and execution environment need not be the same.8
Matching verifies that the selected executor claims and demonstrates the requested environment. It does not yet prove isolation between actions, credentials, cleanup, or safe image evolution. 6.3.9 Remote Executor Environments and Isolation explains those executor lifecycle and runtime controls.
Require positive placement and negative rejection
A compatible success proves only half of matching. For one representative action class, capture and join:
- the action's assigned execution group, Bazel's selected execution platform, and separate toolchain-resolution/configured-tool evidence;
- the effective execution properties sent for the action;
- the backend's interpreted requirement set and selected bounded pool;
- the operation, executor identity, and environment/image version;
- an uncached remote result for that attempt, with local fallback excluded and remote cache acceptance disabled or equivalently bypassed for the pinned Bazel/backend version.
The exact placement log, UI, or API is backend-specific. The required evidence
shape is not: it must connect the Bazel action to one actual executor whose
environment satisfies the promise. Retain evidence equivalent to REAPI's
cached_result == false; metadata returned with an earlier cached result does
not prove a fresh placement attempt. An endpoint connection, an advertised
capability, or a successful build without executor identity is not enough.
Then run negative probes. Change one supported requirement to an incompatible value and send one unknown or ambiguous value. Both requests should reject visibly before incompatible execution. A default pool that accepts either probe has hidden a gap rather than solved it.
Classify: The request contains the expected properties, and backend evidence shows that it reached the intended pool. The action then fails because the executor image contains an older runtime than the pool contract specifies. Which boundary passed, which failed, and what observation settles the diagnosis?
Reveal
Protocol transport and backend routing passed: the expected properties reached the intended pool. The executor promise failed. Join the action and executor identity to the actual image or runtime revision, then compare that revision with the version promised for the pool. Changing scheduling priority or the platform label would not repair a stale executor environment.
When either probe fails, write the result back to the readiness register from 6.3.1 Making Actions Work Remotely as a specific property or environment gap. The remediation may change a rule/toolchain requirement or the fleet translation, but it should not broaden every executor image or enable fallback merely to make the action green. 6.3.13 Remote Action Failure Handling defines retry and fallback policy; 6.3.18 Remote Execution Production Readiness later combines matching with the other production qualification gates.
The portable matching contract also becomes an input to 6.3.3 Choosing a Remote Execution Backend. A backend candidate must be able to represent the required properties, route them to bounded compatible capacity, expose placement evidence, and reject unsupported combinations. Product selection begins from that contract, not from a feature checklist.
Bazel resolves the action's assigned execution group's platform and toolchain and computes its effective properties; REAPI carries those properties; a particular backend gives them meaning and restricts the action to a bounded pool; the concrete executor must then provide the promised environment. Neither a platform label nor a vendor property provisions a worker or proves compatibility.
Accept matching only with both kinds of evidence: a representative action is joined to the intended compatible executor and environment, while incompatible, unknown, or ambiguous requirements reject visibly. Record every failure as a specific property or environment gap instead of hiding it with a catch-all pool or local fallback.
Check your understanding · 3 questions
1.Match each part of the remote-matching chain to the evidence or responsibility it represents:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
2.Which practices make execution-property routing a dependable matching contract?
Select all that apply
3.True or false: what evidence is needed before accepting an action class as correctly matched?
Choose True or False for each sentence
Footnotes
-
Platforms and Toolchains Rules — separate execution/target toolchain constraints and opaque forwarding of platform
exec_properties↩1 ↩2 -
Adapting Bazel Rules for Remote Execution — host, execution, and target roles plus platform-compatible toolchain binaries ↩
-
Execution Groups — per-group platform/toolchain resolution and execution-property overrides ↩1 ↩2
-
Remote APIs — protocol contracts for caching and remote execution —
Platformtransport and the boundary between protocol semantics and server-specific matching ↩ -
Embedded platform testing with Remote Execution — EngFlow-specific pool property routing embedded tests to ARM workers ↩
-
Lessons From Routing Remote Actions at Scale - Son Luong Ngoc, BuildBuddy — BuildBuddy-specific pools and routing inputs ↩
-
Bringing Bazel to Windows, then and now — Windows execution platform, container-image property, toolchain, and executor-environment requirements ↩
-
State of Swift and iOS in Bazel — Linux clients initiating iOS builds on a macOS/Xcode-capable fleet ↩