Hive Hive
Sign in

fix(server): correct run attribution and harden xcresult processing

GitHub issue · Closed

Metadata
Source
tuist/tuist #11435
Updated
Jun 24, 2026
Domains
Compute Storage
Details

Describe here the purpose of your PR.

What

Two related fixes to build/test run processing:

  1. Run attribution no longer shows the organization name in the “Ran by” badge for organization-project runs. The run’s account_id is stamped with the authenticated user’s account again, while the processing workers resolve the storage backend from the project’s account.
  2. xcresult processing no longer hard-fails (and Sentry-spams) when an uploaded bundle has no action log, and parse failures now carry actionable diagnostics.

Why / root cause

Attribution

The build/test run account_id column is dual-purpose: it backs the ran_by_account “Ran by” badge AND was being used to route the processing worker’s S3 download. A prior fix (#11307 / #11381) set account_id to the project’s account so the worker would download from an account-specific (custom S3) bucket. That fixed storage but regressed attribution: every organization-project run started showing the organization’s name instead of the developer who ran it.

This PR decouples the two concerns:

  • builds_controller / tests_controller create stamp account_id with Authentication.authenticated_subject_account(conn) (attribution), matching the existing runs_controller convention.
  • ProcessBuildWorker / ProcessXcresultWorker resolve the storage backend from Projects.get_project_by_id(project_id).account. The storage key and custom S3 config are both namespaced under the project’s account, so storage is intrinsically a function of the project, not of who ran the build.

This keeps the storage-routing fix from #11307 / #11381 intact while restoring correct attribution.

xcresult processing

After the storage fix, artifacts download correctly and reach the parse stage, so the remaining “failed processing” reports are genuine xcresult parse failures in the Swift NIF. One class was reproduced end to end: an aborted or test-less run uploads an xcresult with zero test nodes and no action log. xcresulttool get log --type action then prints “No action log available” and writes nothing, and the parser fed that empty output into JSONDecoder().decode(ActionLogSection), which throws the opaque “The data couldn’t be read because it isn’t in the correct format.” That failed the whole run, retried five times, and alerted Sentry each time.

The action log only enriches durations and failure locations, so its absence must not abort the parse. XCResultParser.actionLog now returns an empty section when xcresulttool emits nothing.

A second class (Failed to parse xcresult output, most likely the 600s parse timeout on large bundles) could not be reproduced from logs, so this PR also improves diagnostics:

  • Decode failures now report the step (test-results / action-log), the DecodingError kind and key path, and a short head of the offending output, instead of the schema-blind “data couldn’t be read”.
  • The 600s NIF timeout reports a distinct “timed out after 600s” message instead of reusing failedToParseOutput.

Impact

  • New organization-project build/test runs attribute correctly to the developer (“Ran by ”) while continuing to upload and process through the project account’s storage backend.
  • Aborted or empty xcresult bundles process to a zero-test run instead of looping on failed_processing and alerting Sentry.
  • Future parse failures surface actionable detail in Sentry.
  • Existing runs already written with the organization account are historical data and remain unchanged.

How to test locally

Server (attribution + storage decoupling):

cd server
mix test test/tuist/builds/workers/process_build_worker_test.exs \
test/tuist/tests/workers/process_xcresult_worker_test.exs \
test/tuist_web/controllers/api/builds_controller_test.exs \
test/tuist_web/controllers/api/tests_controller_test.exs

35 worker + 46 controller tests pass. The controller suite needs hosted mode if your local test license is stale: TUIST_HOSTED=1 mix test ....

xcresult NIF (action-log resilience + diagnostics):

cd server/native/xcresult_nif && rm -rf .build && swift test --replace-scm-with-registry --filter XCResultParserTests

9 tests pass, including a new regression test that routes the two xcresulttool reads to an empty test-results payload and an empty action log and asserts the parse succeeds.

Comments

No GitHub comments yet.