Examples below assume the generated repository is named
test_optimization_data. If you used a different name, replace labels and
repository names accordingly. For config-gated Go and Python repositories,
include --config=test-optimization in sync, test, doctor, and uploader
commands. Other companions retain their current activation contract.
If Bazel reports that sync requires WORKSPACE support, add
--enable_workspace to sync commands in this document.
For config-gated Go and Python onboarding,
--config=test-optimization is the single user-facing enablement switch. Both
languages set the metadata repository environment:
common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1
Go additionally sets the existing rules_go Orchestrion flag:
build:test-optimization --@rules_go//go/private/orchestrion:enabled=true
When the config is omitted, the public Go extension's config-gated default and
repositories explicitly configured with enabled_by_env = True generate the
documented no-fetch stubs. Go aliases select local empty targets; Python keeps
the normal consumer runner without Test Optimization metadata or payload
wiring. Python-only consumers omit the Go line. For WORKSPACE Go, replace
@rules_go with the apparent repository name used by that workspace. Java,
NodeJS, .NET, and Ruby retain their existing enablement contract in this
release.
| Symptom | First checks | Likely section |
|---|---|---|
| No files fetched or stale data | DD_API_KEY forwarded with --repo_env; use FETCH_SALT only for an explicit force-refresh sync |
Repository rule not fetching data |
| Uploader says no payload files | tracer file-mode contract + payload files under bazel-testlogs/*/test.outputs/ |
Uploader not finding payloads |
| Doctor reports msgpack payloads | tracer is not in Bazel JSON file mode | Doctor failures |
| Doctor reports missing Git or Bazel metadata | sync metadata context or sidecar metadata is absent | Doctor failures |
| Uploaded tests miss Git or Bazel tags | run uploader dry-run enrichment validation | Uploader enrichment dry-run |
| Upload network errors | credential mode (agentless vs EVP), intake reachability | Tests not uploading (network errors) |
| CI failure requires log archaeology | archive the support bundle from the failing run | Collect diagnostic reports |
| Module selection misses | bazel query for module_* targets and importpath/module label expectations |
Per-module files not found |
| Go build fails with a tracer version mismatch | dd_trace_go_pin_files, explicit version escape hatches, local go.mod/go.sum |
Go tracer version drift |
| Bazel resolves an older tracer or Orchestrion module in WORKSPACE mode | checked-in go_repository(...) pins |
WORKSPACE go_repository drift |
| WORKSPACE archive pins fail after a PR was squash-merged | generated pins commit reachability and archive SHA | Published Go pins |
| Private/internal WORKSPACE fetch returns 404 | SSH git or authenticated archive access | Private repository fetch |
| Bazel downloads unrelated toolchains or analyzes unrelated packages | cold monorepo state or root-package doctor/uploader placement | Monorepo analysis cost |
| Windows path/policy failures | PowerShell policy + path separators | Windows-specific issues |
When a failure is hard to classify from logs alone, archive a support bundle from the same run. For the simplest customer ask after tests have already run, use the doctor directly:
bazel run --config=test-optimization //:dd_test_optimization_doctor -- \
--support-bundle .topt/reports/dd-test-optimization-support.zipFor BEP/BwoB CI investigations, include the same BEP and artifact flags used by the failing job:
bazel run --config=test-optimization //:dd_test_optimization_doctor -- \
--bep-json="$bep_json" \
--freshness-source=bep \
--freshness-mode=required \
--artifact-source=bep \
--artifact-staging-dir="$artifact_staging_dir" \
--support-bundle=.topt/reports/dd-test-optimization-support.zipThe doctor bundle is doctor-only: it captures expected targets, BEP freshness, artifact staging, discovered payload directories, payload counts, status, exit code, effective doctor flags, runtime metadata, and selected BEP summaries. It does not prove uploader dry-run, enrichment, API-key, or upload behavior.
Use this escalation ladder:
| Situation | Ask for | Why |
|---|---|---|
| First customer response after tests already ran | Doctor --support-bundle=<path> |
Smallest command; built into the doctor target; no helper scripts required |
| CI failure where upload, enrichment, or dry-run behavior matters | Wrapper --report-dir=<path> --support-bundle=<path> |
Includes doctor, uploader dry-run, optional upload report, BEP summaries, and effective wrapper flags |
| Repository cannot run the wrapper or doctor bundle | Raw --report-json files plus manual create_support_bundle.py output |
Fallback only; raw reports may include internal paths until the redacted zip is created |
Ask the customer to attach the zip, not screenshots of terminal output. If the
bundle is not available, ask for the individual JSON reports and the exact
bazel test, doctor, and uploader commands.
Use the wrapper flow when the CI job should also validate uploader dry-run or perform the real upload:
# Vendor the full tools/test_optimization/ helper directory, or set
# DD_TEST_OPTIMIZATION_SUPPORT_BUNDLE_COLLECTOR to create_support_bundle.py.
tools/test_optimization/run_test_optimization_ci.sh \
--report-dir .topt/reports \
--support-bundle .topt/reports/dd-test-optimization-support.zip \
//...# Vendor the full tools/test_optimization/ helper directory, or set
# DD_TEST_OPTIMIZATION_SUPPORT_BUNDLE_COLLECTOR to create_support_bundle.py.
.\tools\test_optimization\run_test_optimization_ci.ps1 `
-ReportDir .topt\reports `
-SupportBundle .topt\reports\dd-test-optimization-support.zip `
//...The wrapper writes doctor-report.json and uploader-dry-run-report.json
under the report directory. If the same wrapper run includes the real upload,
it also writes uploader-upload-report.json, preserving the dry-run report.
When --support-bundle or -SupportBundle is configured, it also writes
dd-test-optimization-support.zip.
Each report has a result.reason_code, human-readable result.reason, and
result.next_steps so support can distinguish cached tests, remote-only BEP
artifacts, missing payloads, enrichment failures, dry-run no-upload, and real
upload failures without reading the full log.
When opening a support ticket, attach
.topt/reports/dd-test-optimization-support.zip. The bundle is redacted by
default and does not contain raw payload files, raw CI logs, raw environment
variables, or the raw BEP. Depending on whether the doctor or wrapper created
it, it contains bounded doctor-only or doctor/uploader reports, selected BEP
summaries, command/flags.json, environment/runtime.json, summary.md, and
diagnostics.json.
Triage the bundle in this order:
- Open
summary.mdfor the human-readable failure classification. - Check
diagnostics.jsonforsummary.status,summary.primary_reason_code, payload counts, and included report paths. - Check
reports/doctor-report.jsonfor expected targets, seen targets, fresh/cached/remote-only BEP outputs, artifact staging, and doctor errors. - If the wrapper created the bundle, check
reports/uploader-dry-run-report.jsonfor payload discovery, enrichment validation, skipped payloads, and no-upload reasons. - If upload was enabled, check
reports/uploader-upload-report.jsonfor the real upload attempt count, HTTP status, and terminal upload failure. - Check
command/flags.jsonto confirm the test run used a unique BEP file and that doctor/uploader ran with the matching--bep-json,--freshness-source=bep,--freshness-mode=required,--artifact-source=bep, and artifact staging flags.
Manual doctor/uploader flow:
bazel run --config=test-optimization //:dd_test_optimization_doctor -- \
--bep-json="$bep_json" \
--freshness-source=bep \
--freshness-mode=required \
--artifact-source=bep \
--artifact-staging-dir="$artifact_staging_dir" \
--report-json=.topt/doctor-report.json
bazel run --config=test-optimization //:dd_upload_payloads -- \
--bep-json="$bep_json" \
--freshness-source=bep \
--freshness-mode=required \
--artifact-source=bep \
--artifact-staging-dir="$artifact_staging_dir" \
--dry-run \
--validate-enrichment \
--report-json=.topt/uploader-report.jsonArchive the JSON files as CI artifacts when a repository cannot use the wrapper support bundle. If the repository has the helper script available, create the same redacted zip from existing reports:
python3 tools/test_optimization/create_support_bundle.py \
--report-dir .topt/reports \
--report-json .topt/reports/doctor-report.json \
--report-json .topt/reports/uploader-dry-run-report.json \
--output .topt/reports/dd-test-optimization-support.zip \
--workspace-root "$PWD" \
--output-base "$(bazel info output_base)"Use repeatable --bep-json=<path> flags with that command when the reports came
from BEP/BwoB runs. If only a Markdown summary is possible, render a short
fallback summary:
python3 tools/test_optimization/render_report_summary.py \
.topt/reports/doctor-report.json \
.topt/reports/uploader-dry-run-report.json \
--output .topt/reports/upload-diagnostics.mdBefore sharing raw reports outside the trusted project boundary, review them for repository paths, target names, and service metadata. They must not contain API keys, but they can contain internal labels and filesystem paths. Prefer a support bundle for external escalation because it redacts and bounds those values automatically.
Symptom: Build succeeds but test optimization files are empty or stale.
Solutions:
-
Verify DD_API_KEY is set:
env | grep '^DD_API_KEY=' grep -n 'common --repo_env=DD_API_KEY' .bazelrc
$env:DD_API_KEY Select-String -Path .bazelrc -Pattern "common --repo_env=DD_API_KEY"
If missing, set
DD_API_KEYin your shell/CI secret store and add to.bazelrc:common --repo_env=DD_API_KEY -
Force refetch only when intentional with a cache-busting salt:
bazel sync --config=test-optimization --only=<repo_name> --repo_env=FETCH_SALT="$(date +%s)"
bazel sync --config=test-optimization --only=<repo_name> --repo_env=FETCH_SALT="$(Get-Date -UFormat %s)"
Do not put
FETCH_SALTin.bazelrc,bazel test, doctor, or uploader commands. It deliberately breaks the repository-rule cache key and should be used only when you want fresh backend metadata. -
Check repository cache to see if the rule ran:
# Find the external repository directory bazel info output_base # Repository contents at: $(bazel info output_base)/external/<repo_name> ls -la $(bazel info output_base)/external/<repo_name>/.testoptimization/
# Find the external repository directory $outputBase = bazel info output_base # Repository contents at: "$outputBase/external/<repo_name>" Get-ChildItem -Force "$outputBase/external/<repo_name>/.testoptimization/"
-
Enable debug logging in your
MODULE.bazel:test_optimization_sync.test_optimization_sync( name = "test_optimization_data", debug = True, # Verbose logging )
Preserve the repository's existing
enabled_by_envvalue; enabling debug logging must not change its activation contract.
These checks apply only to the automatic Go/Python path. Static single-service and static multi-service consumers do not need an invocation manifest.
The aggregate repository intentionally fails before HTTP when enabled without
the command-owned manifest. Run the consumer repository's managed Test
Optimization command. Do not add the internal manifest handoff to .bazelrc
or ordinary CI jobs.
If a direct bazel test --config=test-optimization bypasses the managed
command, the failure is expected: target discovery must run first so the
repository rule receives exact targets and runtime contexts.
One managed command invocation must pass the same temporary manifest path to
test, doctor, uploader dry-run, and optional upload. If the request log shows
another fetch during a post-test phase, check that the command did not create a
new temporary directory or change
DD_TEST_OPTIMIZATION_SERVICES_MANIFEST between child Bazel processes.
A later managed command intentionally creates a new manifest path and fetches
current backend state once. If its selected settings/module files are
unchanged, tests should remain Bazel cache hits even though
telemetry_facts.json contains new request timings.
The error names the schema field rejected before metadata requests. Verify that the consumer resolver emitted:
- schema version
1; - canonical local labels such as
//pkg:test, not patterns or external labels; - unique, used contexts;
- only Go/Python runtimes;
- deterministic context keys matching service and runtime;
applicationordomain_fallbackfor every target.
Fix the resolver or its target input. Do not hand-edit the temporary manifest.
Only targets recognized by the consumer's central Go/Python macro policy are
eligible. A target absent from topt_data_by_target correctly keeps its raw
behavior. Inspect the managed command summary for unsupported labels and
confirm that the target is a runtime test, not a build-only companion.
Java, NodeJS, .NET, and Ruby are intentionally outside automatic manifest onboarding in this release.
The resolver and repository reject two logical services that sanitize to the same context key, conflicting module paths or runtime versions for one service/runtime, duplicate labels, and one label assigned to multiple contexts. Change the consumer's deterministic naming grammar or split the conflicting runtime contexts; do not append positional suffixes whose result depends on input order.
The aggregate :expected_targets file is exact. A mismatch means the execution
set, BEP inputs, or doctor wiring does not match the manifest. The managed
command must test the fully expanded labels from the same discovery phase and
pass that invocation's BEP file to doctor.
Cached tests do not emit fresh payloads. With configured expected targets,
strict BEP freshness accepts those cache hits as covered, validates no stale
local outputs for them, and lets uploader treat an all-cached invocation as a
no-op. If a cached target is reported as missing instead, verify that doctor
received the BEP from the exact matching bazel test invocation. Use separate
cache-isolation tests when validating action-key behavior.
Disabled manifest sync must not read the manifest, contact Datadog, or resolve
the real Orchestrion tool repository. Enabled Go bootstrap uses the
Bazel-managed Go SDK and must not depend on a host go binary. If a host-Go
sentinel fires, verify the patched rules_go profile, generated SDK wiring,
and central wrapper before installing Go on the host.
Symptom: A consumer cannot fetch the published Go/Orchestrion archive, or a pin that worked during review stops working after a squash merge.
Cause: The consumer was given a feature-branch commit, a stale archive hash, or a tuple that was not generated from the real GitHub codeload archive.
Solutions:
-
Regenerate pins from
origin/mainin this repository:./bazelw run //tools/dev:print_go_onboarding_pins -- \ --commit "$(git rev-parse origin/main)" \ --rules-go-upstream v0_60_0 \ --variant base \ --verify-main-reachable -
If you are in a consumer repo, use the bootstrap helper with the squash-merged commit:
bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --print-published-pins \ --rto-commit <published-origin-main-sha> \ --rules-go-upstream v0_60_0 \ --rules-go-variant base
-
Keep the generated tuple together. If
RTO_COMMIT,RTO_ARCHIVE_URL,RTO_ARCHIVE_SHA256, andRTO_ARCHIVE_PREFIXcome from different commits, archive mode will fail or fetch the wrong source. The current published tuple is tracked indocs/Installation_Reference.md. -
Authenticate private archive downloads. The helper uses
GITHUB_TOKEN,GH_TOKEN, orgh auth tokenwhen available. If codeload returns404for a commit you can see in GitHub, refresh local GitHub CLI authentication or set one of those token variables before regenerating pins. -
Use the generated summary for handoff when a monorepo needs a permanent local guide:
bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --write-onboarding-summary=TEST_OPTIMIZATION_GUIDE.md \ --rto-commit <published-origin-main-sha> \ --rules-go-upstream v0_60_0 \ --rules-go-variant base
Symptom: A WORKSPACE consumer gets 404 when fetching the rules archive or
companion archive from a private/internal repository.
Cause: Anonymous codeload archive URLs can return 404 for private
repositories even when the commit exists and the user can see it in GitHub.
Solutions:
-
Prefer SSH git fetch for internal Datadog consumers:
git_repository( name = "datadog-rules-test-optimization", commit = "<commit-sha>", remote = "ssh://git@github.com/DataDog/rules_test_optimization.git", )
-
Use archive fetch only when Bazel has compatible authentication, for example
.netrcor another repository-approved token mechanism. -
Do not commit local archive paths or local checkouts as a workaround for CI. Use local paths only for temporary local validation.
Symptom: Tests already produced payload files, but running doctor or uploader appears slow because Bazel downloads unrelated toolchains or analyzes unrelated packages.
Cause: Some cost is normal cold-cache monorepo behavior. Another common
cause is placing doctor/uploader in the workspace root package, which can load
global root BUILD.bazel wiring unrelated to Test Optimization.
Solutions:
-
Move the logical doctor/uploader pair to a lightweight package:
# tools/test_optimization/BUILD.bazel load("@datadog-rules-test-optimization//tools/core:test_optimization_targets.bzl", "dd_test_optimization_targets") dd_test_optimization_targets( name = "test_optimization", sync_repo_name = "test_optimization_data", expected_targets = ["//app:service_py_test"], )
-
Run package-local labels:
bep_json="${bep_json:-/path/from/matching/bazel-test.bep.json}" bazel run --config=test-optimization //tools/test_optimization:dd_test_optimization_doctor -- \ --bep-json="$bep_json" \ --freshness-source=bep \ --freshness-mode=required bazel run --config=test-optimization //tools/test_optimization:dd_upload_payloads -- \ --bep-json="$bep_json" \ --freshness-source=bep \ --freshness-mode=required \ --dry-run \ --validate-enrichment
-
If Bazel repeatedly refetches Test Optimization metadata, check whether a script or
.bazelrcis settingFETCH_SALTby default.
Symptom: Uploader runs but says "no payload files found".
Solutions:
-
Check if tests wrote payloads:
find bazel-testlogs -name "test.outputs" -type d ls bazel-testlogs/*/test.outputs/payloads/tests/
Get-ChildItem -Recurse -Directory -Path bazel-testlogs -Filter "test.outputs" Get-ChildItem -Recurse -Path bazel-testlogs -File -Filter "*.json" | Where-Object { $_.FullName -match "test\.outputs[\\/]+payloads[\\/]+tests" }
-
Verify tracer support: Ensure your tracer/runtime supports file mode via
DD_TEST_OPTIMIZATION_PAYLOADS_IN_FILESand manifest discovery viaDD_TEST_OPTIMIZATION_MANIFEST_FILE. -
Check DD_TEST_OPTIMIZATION_PAYLOADS_IN_FILES: The macro should set this to
"true". Verify your test environment:bazel test --config=test-optimization //your:test --test_output=all 2>&1 | grep DD_TEST_OPTIMIZATION
bazel test --config=test-optimization //your:test --test_output=all *>&1 | Select-String "DD_TEST_OPTIMIZATION"
PowerShell uses
*>&1(not Bash2>&1) to merge stderr/stdout. -
For RBE/remote-cache users: Put
--remote_download_minimal,--remote_download_regex=.*test[.]outputs.*, and--zip_undeclared_test_outputsin the active test.bazelrcconfig. Run tests with a fresh--build_event_json_file=..., then run doctor/uploader with repeatable--bep-json=<path>,--freshness-source=bep,--freshness-mode=required,--artifact-source=bep, and--artifact-staging-dir=<temp-dir>so cached outputs are skipped and localoutputs.zipcarriers are extracted through BEP staging.
Symptom: bazel run --config=test-optimization //:dd_test_optimization_doctor
fails before upload.
Solutions:
-
Msgpack payloads: Go/Orchestrion Bazel mode must write JSON payloads to
TEST_UNDECLARED_OUTPUTS_DIR. If the doctor finds.msgpackor.msgpack.gz, verify that the target uses the Go test optimization macro or wrapper and that the configureddd-trace-goversion includes Bazel JSON file-mode support. -
No JSON payloads: Confirm the test target actually ran with Test Optimization enabled and inspect:
find bazel-testlogs -path '*/test.outputs/payloads/*/*.json' -type fFor Python WORKSPACE consumers, confirm the target loads
dd_topt_py_testfrom@datadog-rules-test-optimization-python//:topt_py_test.bzland that the companion repository was declared withdatadog_python_test_optimization_workspace_repositories(...). For Pythonrunner_mode = "consumer_runner", also verify that the consumer-owned wrapper really executes pytest, propagates theenvpassed bydd_topt_py_test, and keepsPYTEST_ADDOPTS=--ddtraceunless it intentionally sets--no-ddtrace. Ifenvis aselect(...), the macro cannot addPYTEST_ADDOPTSinside each branch; include--ddtracein every relevant selected environment. The target must also depend onddtraceandpytest. -
Missing Git metadata: The sync metadata fetch must see repository URL, commit SHA, and branch or tag. Put
DD_GIT_*values in.bazelrcascommon:test-optimization --repo_env=DD_GIT_<NAME>, not--test_env. The doctor scans versioned.bazelrcfiles for--test_env=DD_GIT_*, but it cannot detect a bad--test_env=DD_GIT_*flag typed directly on the CLI. -
Missing Bazel metadata: The target should emit
bazel_target_metadata.jsonnext to payload files. Use the companion macro or generated wrapper instead of invoking the raw language test rule directly.For Python consumer-owned wrappers, the wrapper must expose an executable target with
RunEnvironmentInfopreserved from the rawpy_test; otherwise the public metadata wrapper can run but the actual pytest process may miss the requiredDD_TEST_OPTIMIZATION_*environment.In WORKSPACE mode, also verify that the Python helper maps the companion's internal
@rules_pythondependency to the consumer repository name:datadog_python_test_optimization_workspace_repositories( rto_commit = "<commit-sha>", rules_python_repo_name = "rules_python", # or the consumer's custom name )
-
full_bundle_no_match: The Go macro could not map the test target to a per-module bundle. Preferembed = [":lib"]so the macro can read the same importpath thatrules_gouses, or setmodule_label_overrideonly when the module label is intentionally known.moduleandmodule_overrideare valid successful selections;full_bundle_disabledis valid when backend module data is disabled. Generic inferred/derived selection may intentionally use the canonical full bundle and reportfull_bundle_no_match; the doctor rejects that state by default, so only an intentionally generic consumer should setforbid_full_bundle_no_match = False. -
Expected target output missing: Run the exact target listed in
expected_targetsbefore the doctor. With remote execution or remote cache, use the recommended test.bazelrcconfig:--remote_download_minimal --remote_download_regex=.*test[.]outputs.* --zip_undeclared_test_outputs. ExportDD_TEST_OPTIMIZATION_ARTIFACT_SOURCE=bepfor doctor/uploader; if BEP references remote-only artifacts, enable BEP artifact resolution with--artifact-source=bep --remote-artifacts=download.
Symptom: The doctor or uploader is configured with BEP freshness, but
loose test.outputs/payloads/... files are not present under bazel-testlogs/
because the Bazel invocation used remote execution/cache, zipped undeclared
outputs, or left the selected BEP artifacts remote-only.
Solutions:
-
Prefer selective local materialization plus zipped undeclared outputs when Bazel can download the needed outputs:
test:test-optimization --remote_download_minimal test:test-optimization --remote_download_regex=.*test[.]outputs.* test:test-optimization --zip_undeclared_test_outputs -
Pass the same BEP file to doctor/uploader and enable local BEP artifact staging. The checked-in CI wrapper does this with repeatable CLI flags:
tools/test_optimization/run_test_optimization_ci.sh //...
Manual invocations should use the same temporary BEP and staging paths:
bep_json="${bep_json:-/path/from/matching/bazel-test.bep.json}" artifact_staging_dir="${artifact_staging_dir:-$(mktemp -d "${TMPDIR:-/tmp}/dd-topt-artifacts.XXXXXX")}" bazel run --config=test-optimization //:dd_test_optimization_doctor -- \ --bep-json="$bep_json" \ --freshness-source=bep \ --freshness-mode=required \ --artifact-source=bep \ --artifact-staging-dir="$artifact_staging_dir" bazel run --config=test-optimization //:dd_upload_payloads -- \ --bep-json="$bep_json" \ --freshness-source=bep \ --freshness-mode=required \ --artifact-source=bep \ --artifact-staging-dir="$artifact_staging_dir" \ --dry-run \ --validate-enrichment
--remote-artifactsis not required for localoutputs.zipfiles. -
If CI cannot download
test.outputsoroutputs.ziplocally, pass the same BEP file to doctor and uploader and enable remote staging. HTTP/HTTPSoutputs.zipcarriers can be staged without a downloader:artifact_staging_dir="${artifact_staging_dir:-$(mktemp -d "${TMPDIR:-/tmp}/dd-topt-artifacts.XXXXXX")}" bazel run --config=test-optimization //:dd_test_optimization_doctor -- \ --bep-json="$bep_json" \ --freshness-source=bep \ --freshness-mode=required \ --artifact-source=bep \ --remote-artifacts=download \ --artifact-staging-dir="$artifact_staging_dir" bazel run --config=test-optimization //:dd_upload_payloads -- \ --bep-json="$bep_json" \ --freshness-source=bep \ --freshness-mode=required \ --artifact-source=bep \ --remote-artifacts=download \ --artifact-staging-dir="$artifact_staging_dir" \ --dry-run \ --validate-enrichment
-
For bytestream/CAS/custom provider BEP artifact references, or HTTP/HTTPS endpoints that require custom auth, configure one downloader executable:
export DD_TEST_OPTIMIZATION_BEP_ARTIFACT_DOWNLOADER=/path/to/download-outputs-zipThe executable receives
--uri,--name, and--outputarguments and must write anoutputs.ziparchive to--outputbefore--bep-artifact-downloader-timeout-secexpires. If your provider needs an interpreter, fixed arguments, custom auth headers, mTLS, signed header refresh, or a CAS client, wrap it in a script and configure the wrapper path. The public rule does not ship credentials or a Datadog-internal CAS client.
Native HTTP/HTTPS staging is unauthenticated. It retries transient network failures, truncated responses, HTTP 408, HTTP 429, and HTTP 5xx responses three times with exponential backoff. It does not add custom headers, read credentials, use cookies, or consult netrc. Query strings, fragments, usernames, and passwords are redacted from doctor/uploader warnings and JSON reports.
Common messages:
--artifact-source=bep requires --bep-json or DD_TEST_OPTIMIZATION_BEP_JSON: pass the BEP JSON produced by the matchingbazel testinvocation.BEP artifact is remote-only and no downloader is configured: either use--remote_download_minimal --remote_download_regex=.*test[.]outputs.* --zip_undeclared_test_outputswith--artifact-source=bep, enable--remote-artifacts=downloadfor HTTP/HTTPSoutputs.zipcarriers, configureDD_TEST_OPTIMIZATION_BEP_ARTIFACT_DOWNLOADERfor bytestream/CAS/custom-auth providers, or use--remote-artifacts=disabledoutside required freshness mode.BEP HTTP artifact download failed ... HTTP Error 401or403: the endpoint likely requires auth. Use a downloader wrapper that injects the required credentials or headers.BEP HTTP artifact download failed ... HTTP Error 404: confirm the BEP URI still exists for the current job and has not expired.BEP HTTP artifact download failed ... timed out: native HTTP retries transient failures three times; raise--bep-artifact-downloader-timeout-seconly when each request attempt needs more time.invalid BEP outputs.zip: confirm the URL serves Bazel'soutputs.zip, not HTML, JSON, or an error page.BEP HTTP artifact is too large: inspect undeclared test outputs for unexpected files; the compressed download and extracted tree both have size guards.BEP artifact is not available locally: the BEP points at a local path orfile://URI that does not exist in this workspace. Check that the BEP file matches the current test invocation and machine.remote-artifacts=required: strict artifact mode is all-or-nothing. Missing, ambiguous, unsafe, or undownloadable selected BEP artifacts fail the command instead of falling back to local payloads.unsafe path in BEP outputs.zip: the archive contains an absolute path or parent traversal entry. Regenerate the archive or fix the downloader.BEP outputs.zip has too many entries: the archive exceeds the staging entry limit and is rejected before extraction.BEP outputs.zip is too large after decompression: the archive exceeds the staging decompressed-size limit.BEP artifact staging requires PYTHON, python3, or python: the uploader needs Python only when artifact staging is enabled. SetDD_TEST_OPTIMIZATION_PYTHONorPYTHONwhen Python is not discoverable aspython3orpythononPATH.
Symptom: Payload files exist, but Datadog UI or JSON inspection suggests the uploaded test is missing Git or Bazel tags.
Explanation: Raw test payloads on disk are not the final upload body. The
uploader adds context.json and bazel_target_metadata.json tags immediately
before upload.
Solution: Run the uploader dry-run after tests and doctor:
bep_json="${bep_json:-/path/from/matching/bazel-test.bep.json}"
bazel run --config=test-optimization //:dd_upload_payloads -- \
--bep-json="$bep_json" \
--freshness-source=bep \
--freshness-mode=required \
--dry-run \
--validate-enrichment$env:BEP_JSON = "C:\path\from\matching\bazel-test.bep.json"
bazel run --config=test-optimization //:dd_upload_payloads -- `
--bep-json="$env:BEP_JSON" `
--freshness-source=bep `
--freshness-mode=required `
--dry-run `
--validate-enrichmentDry-run mode does not upload, does not delete payload files, and does not need
DD_API_KEY for agentless mode. By default it requires
git.repository_url, git.commit.sha, bazel.target, and bazel.package in
the enriched test payload. Add
--expected-enriched-tag=bazel.go.payload_selection when a Go rollout must
prove per-module selection. If this fails:
- Ensure the uploader target has the right
data = ["@...//:test_optimization_context"]. - Ensure
bazel_target_metadata.jsonexists beside the payloads. - Ensure
jqis available on Linux/macOS when using--validate-enrichment. - Use
--expected-enriched-tag=<tag>for repository-specific required tags.
Symptom: Uploader cannot find the bazel-testlogs directory.
Solution: Set TESTLOGS_DIR explicitly using the same Bazel flags:
# Bash - use array for multiple flags
BAZEL_FLAGS=("--output_base=/custom/base")
bep_json="${bep_json:-/path/from/matching/bazel-test.bep.json}"
TESTLOGS_DIR=$(bazel "${BAZEL_FLAGS[@]}" info bazel-testlogs) bazel "${BAZEL_FLAGS[@]}" run //:dd_upload_payloads -- \
--bep-json="$bep_json" \
--freshness-source=bep \
--freshness-mode=required# PowerShell
$BazelFlags = @("--output_base=/custom/base")
$env:BEP_JSON = "C:\path\from\matching\bazel-test.bep.json"
$env:TESTLOGS_DIR = (bazel @BazelFlags info bazel-testlogs)
bazel @BazelFlags run //:dd_upload_payloads -- `
--bep-json="$env:BEP_JSON" `
--freshness-source=bep `
--freshness-mode=requiredSymptom: Uploader fails with network errors.
Solutions:
-
Verify credentials:
- Agentless mode requires:
DD_API_KEY,DD_SITE - EVP proxy mode requires:
DD_TEST_OPTIMIZATION_AGENT_URL
- Agentless mode requires:
-
Check firewall/proxy allows HTTPS to:
https://citestcycle-intake.datadoghq.comhttps://citestcov-intake.datadoghq.com- (or equivalent for your
DD_SITE)
-
Enable debug logging:
dd_payload_uploader( name = "dd_upload_payloads", debug = True, ... )
Symptom: dd_topt_go_test fails with "module_X not found" or falls back to
full bundle.
Solutions:
-
List available modules:
bazel query 'kind(".*", @<repo_name>//...)' | grep module_
bazel query 'kind(".*", @<repo_name>//...)' | Select-String "module_"
-
Override module label explicitly (workaround):
dd_topt_go_test( name = "my_test", module_label_override = "my_expected_module", # Matches :module_my_expected_module orchestrion_mode = "test_optimization", ... )
Symptom: Go bootstrap or Bazel build fails with a message showing a
configured tracer version map and a different resolved local dd-trace-go
module version.
Solutions:
-
If you use guided bootstrap, rerun it with the query you want:
bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --dd-trace-go-version <tag-or-sha>
Bootstrap accepts tags, pseudo-versions, branches, and commit SHAs. It rewrites the workspace to the exact resolved versions that Go actually uses. By default it uses targeted module sync, not
go mod tidy. -
If targeted sync reports readonly module errors, rerun bootstrap with explicit targeted sync and the same Go SDK your repository expects:
bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --go-mod-sync=targeted \ --go-binary /path/to/go
If the workspace uses checked-in
go_repositorydeclarations, refresh those declarations aftergo.modorgo.sumchanges so Bazel and the Go module graph agree. -
If you wire Orchestrion manually, use the checked-in module as the normal source of truth:
orchestrion.from_source( dd_trace_go_pin_files = ["@//:go.mod", "@//:go.sum"], go_sdk_root = "@test_optimization_go_sdk//:ROOT", go_sdk_version = "<go-version>", version = "<orchestrion-version>", )
Export both root files from their BUILD package. Bazel resolves all supported direct and transitive tracer modules with hermetic Go and
-mod=readonly; it does not updatego.sum. -
If pin-file resolution reports an absent module, unsupported replacement, or read-only failure, fix the module with its normal dependency workflow. If that graph intentionally cannot use pin-file mode, configure the exact
dd_trace_go_versionsmap as an escape hatch. Do not combine pin files with either explicit version field. -
If you omitted every selection mode, remember the legacy default is
v2.9.0.
The build fails on purpose here. It is preventing Bazel from injecting one set of tracer versions while the local Go module still resolves another.
orchestrion.tool.go still matters, but as required import/config wiring for
Orchestrion, not as the source of truth for tracer versions.
Symptom:
Could not find 'go' binary. Please ensure Go is installed.
Cause: the workspace uses Orchestrion wiring generated before the Bazel-managed SDK contract was added, or a manual block omitted the SDK root and version. Disabled mode still avoids the real repository, but enabled mode must compile the patched Orchestrion binary on a cold cache miss.
Solution:
- For Bzlmod, rerun guided bootstrap with the workspace's real Go toolchain
version:
bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --guided \ --service <datadog-service> \ --runtime-version <go-version> \ --write-bazelrc
- For WORKSPACE, regenerate the reviewed snippet with
--workspace-mode --print-workspace-snippet --runtime-version <go-version>. - Verify the generated SDK version equals the version passed to the
repository's central
go_register_toolchains(...)call and the Test Optimization sync repository.
Do not install Go on the analysis host as the fix. The supported wiring uses the Bazel-managed SDK on a cache miss and can restore a compatible warm Orchestrion bootstrap before materializing that SDK.
Symptom: go.mod and go.sum look correct, but Bazel still resolves an
older github.com/DataDog/orchestrion or dd-trace-go module from a checked-in
repositories.bzl file.
Cause: WORKSPACE repositories often keep generated go_repository(...)
declarations separate from go.mod. Updating the Go module graph is not enough
if the generated repository file still pins old versions.
Solutions:
-
Run bootstrap diagnostics after targeted sync:
bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --workspace-mode \ --write-orchestrion-files \ --go-mod-sync=targeted \ --check-go-repositories \ --go-repositories-file repositories.bzl
The checker compares only the modules bootstrap owns:
github.com/DataDog/orchestrion,github.com/DataDog/dd-trace-go/v2,github.com/DataDog/dd-trace-go/contrib/net/http/v2, andgithub.com/DataDog/dd-trace-go/contrib/log/slog/v2. -
Let your repo-owned refresh command repair the file:
bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --workspace-mode \ --write-orchestrion-files \ --go-mod-sync=targeted \ --check-go-repositories \ --go-repositories-file repositories.bzl \ --go-repositories-refresh-command './tools/update-go-repositories.sh'Bootstrap runs the refresh command only after targeted sync succeeds, then validates
repositories.bzlagain. It never editsrepositories.bzldirectly. -
Print expected versions for manual updates:
bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --workspace-mode \ --go-mod-sync=off \ --check-go-repositories \ --print-go-repository-updates
This is useful when the repository has a custom Gazelle/update-repos flow that must be run manually or in a separate review step.
Symptom: onboarding works in small fixtures but large WORKSPACE repos need a long command sequence, local controls, disk checks, and an explicit upload step.
Solutions:
-
Generate a local validation script instead of hand-copying commands:
bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --workspace-mode \ --write-validation-script \ --bazel-command bzl \ --bazel-config test-optimization \ --sync-repo-name test_optimization_data \ --control-target //pkg/plain:go_default_test \ --expected-target //pkg:go_default_test \ --large-monorepo \ --shutdown-bazel-on-exit
-
Run without upload first:
./tools/test_optimization/validate_go_pilot.sh --no-upload
-
Upload only after tests and doctor pass:
DD_API_KEY="$DD_API_KEY" DD_SITE="$DD_SITE" \ ./tools/test_optimization/validate_go_pilot.sh --upload
The generated script never deletes caches. In --large-monorepo mode it warns
when free disk drops below --min-free-disk-gb, runs phases serially, and can
shut down Bazel on exit. It still depends on the normal Bazel config for
--remote_download_minimal --remote_download_regex=.*test[.]outputs.* and
--zip_undeclared_test_outputs, plus BEP runtime configuration through
DD_TEST_OPTIMIZATION_* or equivalent CLI flags; no rule can force that
client-side Bazel behavior.
Symptom: PowerShell errors or path issues.
Solutions:
-
Verify PowerShell execution policy:
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
-
Check paths use forward slashes in Starlark/Bazel contexts (backslashes are auto-converted).
-
Use native PowerShell harness on Windows:
.\tools\tests\integration\run_mock_server_tests.ps1- Git Bash is not required for Windows integration runs.
- Prefer explicit env assignment over shell interpolation in troubleshooting
commands:
- Unix:
DD_API_KEY="$DD_API_KEY" DD_SITE="$DD_SITE" bazel run --config=test-optimization //:dd_upload_payloads - PowerShell: set
$env:DD_API_KEYand$env:DD_SITEfirst, then runbazel run --config=test-optimization //:dd_upload_payloads
- Unix:
- Quote paths containing spaces and avoid
eval-style wrappers. - For refetch debugging, use:
bazel sync --config=test-optimization --only=<repo_name> --repo_env=FETCH_SALT=<timestamp>- if required by workspace mode:
bazel sync --enable_workspace --config=test-optimization --only=<repo_name> --repo_env=FETCH_SALT=<timestamp>
If issues persist:
-
Enable debug mode and capture full output:
bazel sync --config=test-optimization --only=<repo_name> --repo_env=FETCH_SALT=<timestamp> 2>&1 | tee debug.log
bazel sync --config=test-optimization --only=<repo_name> --repo_env=FETCH_SALT=<timestamp> *>&1 | Tee-Object -FilePath debug.log
-
Collect diagnostic info:
- Bazel version:
bazel version - OS:
uname -a(Linux/macOS) orsysteminfo(Windows) - Repository rule outputs (as shown above)
- Doctor and uploader JSON reports, if the failing run produced them
- Sanitized logs (remove API keys before sharing)
- Bazel version:
-
File an issue:
- open an issue in the repository issue tracker with sanitized logs