Skip to content

Build1 publisher3 min readPublished

Play's "no countries" error described the empty alpha track the pipeline was aimed at

Two Android apps shared one publish-track setting. The student app's live track happened to be named alpha, so it shipped; the staff app's real release sat in a custom track, and Google reported alpha's empty state accurately.

The Engineer · Build desk

Illustration accompanying Play's "no countries" error described the empty alpha track the pipeline was aimed at

What happened

  • One of two Android apps in a shared release pipeline shipped cleanly while the other failed every attempt with the same Play message: 'Release in track targeting no countries'.
  • Listing every track for the failing app returned production, beta, alpha and internal all empty and never used, plus a fifth custom-named track holding the live release with real testers.
  • The pipeline was publishing to alpha, a track that had never had a release or countries configured, so Google's error accurately described a track nobody had used.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A single publish-track value can only serve two apps if both happen to use identically named tracks, so the setting has to be scoped per app before a second app joins the pipeline.
  • exposure A pre-commit YAML linter will pass a workflow file that GitHub Actions refuses, so the only place the defect appears is a run history with a missing job.
  • decision Treating a green build as permission to publish is now the thing under review: the release gate has to name which app and which track received the version code.
  • cost The team spent its debugging time on plausible theories that were checkable in Play Console and aimed at the wrong track.

Google's message was accurate. It reported the state of the track the pipeline asked about, and the pipeline was asking about `alpha` [7]. The countries the team kept re-checking in Play Console belonged to a different track: a custom-named one they had created themselves, holding the live release with real testers and real country availability [6]. Three theories went past that. The last one was closed off by a documented limit, according to the dev.to write-up: a track's country availability is not writable through Google's own API for anything other than the production track [5].

Asking Play for every track the staff app had returned five [6]. Four of them, the standard `production`, `beta`, `alpha` and `internal` names, were empty and had never been used [6][1]. One shared "publish to this track" value therefore had four ways to be wrong and one to be right. The student app's active track happened to be named `alpha` literally, which is the only reason half the releases worked [8]. The reported fix is one track variable per app [9].

The second defect is a parse failure in the workflow file. The first attempt at per-app result reporting put an Actions expression inside a shell assignment: `result="${{ failure() && 'failure' || 'success' }}"` in a `run:` block [10]. `success()`, `failure()`, `cancelled()` and `always()` are only valid inside an `if:` condition, and using them anywhere else, including inside `run:` wrapped in `${{ }}`, makes the entire workflow file invalid [13]. The run came back with zero jobs, and its name fell back to the literal file path instead of the workflow's name, which the write-up identifies as GitHub's signature for a file it could not parse [11].

A YAML gate in pre-commit does not catch this. `yaml.safe_load()` in Python, or any plain YAML linter, reports the file as completely fine, because as YAML it is [12]. The file is well-formed and the expression is in the wrong place, and only the Actions parser knows the difference.

The structural call the team made was that "code is production-ready" and "ship this specific app to the Play Store" had to be two separate decisions, not one automatic consequence of the other [15]. The concrete change described in the post is the per-app track variable [9]; the write-up states the decision without publishing the gate that implements it, and genericizes the track name and version code [6].

For the track failure to transfer to your pipeline, one of two things has to be true: your live testing track was created under a name of your own rather than one of Google's four, or a single config value feeds more than one app. Both were true here [6][7]. The YAML failure transfers more widely, because it only requires that someone move an expression function out of an `if:` and into a `run:` [13]. This is part 2 of a five-part series; parts 3 and 4 cover an Actions storage-quota crisis [1][14].

What to watch

  • Parts 3 and 4 of the series cover the Actions storage-quota crisis and the fix the team settled on.
  • Whether GitHub reports a parse error for expression functions used outside an if:, instead of a run with zero jobs named after the file path.
  • Whether the per-app track variable holds up when a third app or a renamed track joins the pipeline.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories