1.2.3 Test Tags
Test metadata does two different jobs in Bazel. Most teams use ordinary strings such as unit, integration, lint, or smoke so --test_tag_filters can carve the repository into useful lanes. A smaller built-in set changes Bazel's own behavior: manual changes wildcard expansion, exclusive changes scheduling, external changes caching, and requires-network matters when tests need an escape hatch from stricter sandbox policy1,2,3.
Tags Are Not Sizes or Dependency Boundaries
Do not mix test tags with 1.2.2 Test Sizes & Timeouts or 1.2.4 testonly Attribute. Size and timeout tell Bazel how heavy a test is. testonly constrains who may depend on a target. Tags answer a different question: should this test be selected for this invocation, or does Bazel need to treat it specially once it runs?1,2
That is also why tags and 1.2.6 Test Suites are complementary rather than interchangeable. A suite gives you a named bundle. Tags stay attached to individual targets and let the CLI slice across package boundaries, which is why teams usually encode categories like unit, integration, or smoke in tags and keep suites for stable named sets2,3.
manual Happens Before Tag Filters
0.2.3 Target Patterns already covered the headline rule: manual removes a target from wildcard patterns such as //..., :all, and :*. The operator-important consequence is that manual is not a normal category tag you can recover later with --test_tag_filters. Wildcard expansion happens first, and only then does Bazel apply test filters to the surviving test targets1,2.
The target-patterns snippet reproduces the behavior with a real java_test marked tags = ["manual"]:
$ bazel query //...
//app:lib
//app:lib_test
//app:server
$ bazel test //...
INFO: Found 2 targets and 0 test targets...
ERROR: No test targets were found, yet testing was requested
$ bazel test //... --test_tag_filters=manual
INFO: Found 2 targets and 0 test targets...
ERROR: No test targets were found, yet testing was requested
bazel query //... still sees the target, because query is an inspection tool rather than a wildcard test runner1. But once you ask Bazel to execute tests selected by //..., the manual target is already gone from the request set. If you really want it, you must name it explicitly, for example bazel test //app:lib_test.
This is the practical boundary between manual and ordinary classification tags. Use manual when a test should stay out of all broad sweeps by default. Use normal tags when the test should remain part of wildcard-based workflows but move between lanes such as PR, integration, or nightly runs.
Use --test_tag_filters For Normal Categories
--test_tag_filters is the right tool for project vocabulary. The flag keeps tests that match at least one positive keyword and rejects any test carrying an excluded keyword2. That gives you commands like:
bazel test //... --test_tag_filters=unit,-flaky
bazel test //... --test_tag_filters=integration,-external
This scales better than encoding CI policy in target names or directories. The BUILD file stays close to the test, while the CLI decides which slice to run today3,4. It also keeps broad commands readable: one repo can expose fast lint and unit lanes, slower integration lanes, and occasional smoke sweeps without inventing a new suite for every combination1,3.
If the point of the invocation is "run the selected tests and nothing else", add --build_tests_only. Without it, bazel test can still build non-test targets named on the command line, as well as tests filtered out of execution. With it, Bazel limits top-level build work to the unfiltered concrete test targets and test_suite targets it plans to run. Their production dependencies still build as needed2,4.
Built-In Modifiers Trade Convenience For Control
Some keywords tell Bazel that a target breaks the normal fast-parallel-cached assumptions instead of merely grouping it1,3.
exclusive means "run no other test at the same time"1. Reach for it when tests fight over a shared emulator, container, database, or other singleton resource3. It is a scheduling escape hatch, so it should be rare.
flaky lives in the same operator conversation, and you can filter on -flaky
from the CLI, but BUILD files usually express the retry behavior with the
dedicated flaky = True attribute2,3. Bazel then allows three attempts by
default for such tests, while --flaky_test_attempts=N lets CI raise or override
that retry policy for a whole invocation2,4.
Retries limit a flaky test's immediate impact. They do not fix the underlying flake.
H.7.7 Flakiness explains how teams assign responsibility and quarantine
unreliable tests.
external disables test-result caching for tests that depend on external state1. That makes it useful for cases where rerunning is safer than pretending the result is reproducible.
requires-network Only Makes Sense Under A Stricter Policy
requires-network is best understood as an escape hatch for hermetic teams, not as a default mode for integration tests. It becomes meaningful when you deliberately tighten sandbox networking, typically with --sandbox_default_allow_network=false, and then opt specific tests back into network access5,6.
That is why this keyword really points forward to 2.3 Hermeticity & Sandboxing and 3.2.6 Hermeticity Settings. The tag provides a narrow exception when the repository keeps network access off by default. It does not bless general non-determinism.
The Level 1 skill is to separate these categories cleanly: manual for wildcard exclusion, ordinary tags for repo-specific selection, and built-in modifiers only when you need a scheduling, retry, caching, or networking exception. Later, H.7.2 Evidence Portfolio turns that same vocabulary into repository-wide policy about what should run on every change and what should stay in slower lanes.
Use manual when wildcards should ignore a test entirely. Use ordinary tags plus --test_tag_filters when you want selectable test lanes. Treat exclusive, flaky, external, and requires-network as escape hatches that trade some of Bazel's default parallelism, caching, or hermeticity for a specific operational need.
Check your understanding · 3 questions
1.True or false: how does the manual tag interact with wildcards and --test_tag_filters?
Choose True or False for each sentence
2.Match each built-in test tag to the behavior it modifies:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
3.Which flags help you narrow the tests Bazel plans to run, without also building explicitly named non-test targets?
Select all that apply
Footnotes
-
Test encyclopedia — special test tag conventions,
manualwildcard exclusion,bazel queryignoringmanual,exclusive,external, andsmoke↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Commands and Options —
--test_tag_filters,--build_tests_only, filtering semantics, and--flaky_test_attempts↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
How to set up a Bazel testing configuration: comprehensive guide for Scala and Java — practical custom-tag workflows,
--test_tag_filtersexamples,exclusive, andflaky = True↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
BazelCon 2019 Day 2: Building a great CI w/ Bazel — CI practice for excluding
manual, using--flaky_test_attempts, and pairing wide test sweeps with--build_tests_only↩1 ↩2 ↩3 -
Bazel Training 101 (Part 15): Flags and Tags — operator-facing summary of special test keywords, including
requires-network↩ -
.bazelrc flags you should enable —
--sandbox_default_allow_network=falseand the role ofrequires-networkas an opt-out ↩