Hive
fix(server): return a suite shard plan instead of a 500 when the client omits enumerated suites
GitHub issue · Closed
What changed
Tuist.Shards.resolve_units/4 for suite granularity now pattern-matches a non-empty list ([_ | _] = suites) before using client-supplied test_suites, so a present-but-nil value falls through to server-side history derivation instead of being passed downstream as the unit list. modules gets the same nil-coalescing treatment via a shared params_modules/1 helper, so the "module" granularity path can’t hit the same crash.
Follow-up to #11581.
Why — the 500
After #11581, the CLI no longer boots every test bundle to enumerate suites. A suite-granularity request therefore arrives with modules populated but no test_suites. The controller always builds params via:
modules: Map.get(body_params, :modules),
test_suites: Map.get(body_params, :test_suites),
so the key is present with a nil value, not omitted.
The old code read it with Map.get(params, :test_suites, []). The [] default only applies to a missing key — a present-but-nil value returns nil. The guard suites when suites != [] then matched (nil != [] is true), and nil flowed downstream as the unit list. The first Enum call over it crashed, surfacing to the client as:
Failed to create shard plan due to an unknown server response of 500.
This is exactly what Simon hit after #11581 merged.
Root cause
Map.get(map, key, default) returns default only when the key is absent. Once the controller normalized every optional field to Map.get(body_params, field) (present key, nil value), the [] defaults inside resolve_units/4 were dead, and a nil leaked into enumeration.
Why this solution over the obvious alternative
Defaulting in the controller (Map.get(body_params, :test_suites) || []) would fix this one call site but leave resolve_units/4 fragile for any other caller. Guarding on the shape actually required — a non-empty list — makes the domain function correct regardless of whether callers pass nil, [], or an omitted key, and routes the empty/nil case to the intended history-derived path.
Validation
- Added
Tuist.ShardsTestcase: suite granularity withtest_suites: nilderives suites from history and produces the expected plan. - Added
TuistWeb.API.ShardsControllerTestcase: posting a suite-granularity plan withmodulesand notest_suitesreturns200with a validshard_count/shards. - Local full-suite run was wedged on compile; relying on CI to validate.
Note
mix.lock carries a transitive cowboy 2.16.1 → 2.17.0 / cowlib 2.17.1 → 2.18.0 bump. The resolver pulls it in and flags the older cowlib as vulnerable, so it rides along here rather than being reverted.
🤖 Generated with Claude Code
Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.
Start a Flight and preserve its objective, outcome, and session here.
No GitHub comments yet.