Repository navigation
Stop committing the Podfile locks, which no job here can regenerate - #495
Merged
Merged
Conversation
The dev build has failed on macOS and iOS since d10fe8a, and nothing on a pull request could have caught it: [!] CocoaPods could not find compatible versions for pod "Firebase/CoreOnly": In snapshot (Podfile.lock): Firebase/CoreOnly (= 12.15.0, ~> 12.15.0) In Podfile: firebase_core ... was resolved to 4.15.2, which depends on Firebase/CoreOnly (~> 12.19.3) #492 moved `firebase_core` 4.11.0 -> 4.15.2. Its podspec asks for a newer Firebase pod than the committed lock names, and `pod install` keeps a locked version for a transitive pod even when the development pod above it changed - which is what CocoaPods means by its second suggestion, "changed the constraints of dependency `Firebase/CoreOnly` inside your development pod `firebase_core`. You should run `pod update Firebase/CoreOnly`". **The message Flutter prints on top of that is misleading, and worth knowing about before it costs somebody a day.** It ends with "CocoaPods's specs repository is too out-of-date to satisfy dependencies. To update the CocoaPods specs, run: pod repo update", which is not a diagnosis: `cocoapods.dart`'s `_diagnosePodInstallFailure` substring-matches `out-of-date source repos` in CocoaPods' output, and CocoaPods prints that phrase for *both* of the two causes it lists. The index was not stale and `pod repo update` fixes nothing here. The verbose log of run 43110342556 settles it: the runner has no baked index at all - `Adding spec repo 'trunk' with CDN` creates it during the run - and `Specs/0/3/5/Firebase/12.19.3/Firebase.podspec.json` is downloaded and read seconds before the error. The version the resolver needed was in front of it. `pod install --repo-update` would fail identically, because refreshing sources does not unlock a pinned pod. So the snapshot is the problem, and the snapshot cannot be maintained here. Committing `Podfile.lock` is the usual advice and it assumes somebody can regenerate it. Nobody can: both build scripts refuse a non-Darwin host, `ci.yml` is `analyze` plus the BSD script tests and builds no Apple target, and the release job is the only thing in this project that ever runs `pod install`. A plugin bump therefore invalidates two tracked files that no reviewer, no gate and no local run can notice, and the first sign is a red release. What is lost by not committing them is narrow: `pubspec.lock` still pins every plugin to an exact version and each plugin's podspec pins the pods it needs, so what floats is a third-party pod's patch range inside a `~>` it already declared. What is gained is a release that does not break on every plugin bump. The release job now uploads the pair it resolved as `podfile-locks`, so the decision is reversible from real files rather than from a guess - commit that pair and drop the two `.gitignore` lines. Those steps are last in the job and `continue-on-error`, because the asset upload above them carries no `if:` and so defaults to `success()`: a diagnostic step failing ahead of it would skip the DMG and IPA upload, skip `publish`, and lose a release that was already built. That is the hazard the Sentry step guards against the same way. This is the shape t16 names. #492's own CI was green because no job on a pull request compiles anything.
mishamyte
added a commit
that referenced
this pull request
Nov 14, 2026
…ed it (#499) * Read the Xcode version without a pipe, which aborted xcodebuild `have="$(xcodebuild -version | head -n 1)"` killed the release run on 2ca67ad. `head` exits once it has the line it wanted, the next write finds the pipe closed, and `xcodebuild` answers that by raising `NSFileHandleOperationException`, printing a backtrace and aborting with 134. `pipefail` makes that the step's status, so "Select Xcode 26 or newer" failed before it had selected anything, the macOS and iOS builds never ran, and `Assert the release published` failed behind them. Android, Linux and Windows were green on that same push. It is a race, not a constant break, and it had passed six times before it fired - every push since the step shipped in #490. Counting green runs understates that: the step also passed on d10fe8a and 2bfa773, whose runs failed later at `Build macOS release` on a stale committed Podfile.lock, which #495 then fixed. Checked by grepping each run log for the step's own closing line; the six print `Xcode 26.6 from /Applications/Xcode_26.6.app, of 14 candidate(s).` and 2ca67ad prints only the echoed script. `xcodebuild -version` prints two lines, and whether its second write lands before `head`'s first read is the runner's business. Taking the first line in the shell removes the pipe instead of hardening around it. No behaviour change otherwise: the six version strings that decided the old `case` decide the new one the same way - 26, 26.1, 26.10.1 accepted, 30.0 and 16.4 rejected - and so do a further twelve inputs, empty output and CRLF among them. This repository warns about the construct in `derive_version.sh`, `check_arb_locales.sh` and `summarize_l10n_failure.sh`, and that is now four warnings and no guard. A six-line grep appended to `lint_workflows.sh:50`, in the shape of the `cond && 0 || 1` guard already there, is green across the workflows at HEAD and red on this step at 2ca67ad. Deliberately not in this commit, which is a five-minute fix to an unpublishable release; it is the obvious next one. Also rewrites the `shell: bash` paragraph, which was wrong before this branch and which fixing a line number would have left wrong. `shell: bash` supplies `-e` and `-o pipefail`, not `-u` - the failed run prints the resolved `/bin/bash --noprofile --norc -e -o pipefail {0}` - and the `-u` comes from the step's own `set` line. Its cross-reference was a bare line number into this file, the only one of its kind here and never once correct: written as `:1014` when the paragraph sat at 1057, found at 1111, and arithmetically 1130 today. It now names the step it means, and the authority is `ci.yml`'s note, which is the one that actually argues for `shell: bash`. With no pipeline left in the step, `pipefail` here is belt-and-braces; what `shell: bash` is now load-bearing for is `$'\n'`, which a POSIX `sh` would not parse. No changelog entry. docs/releasing.md names this case directly - "Most should not - a CI fix, a Crowdin sync, a submodule bump or a test-only branch has nothing a user would read" - and #495 is the precedent beside it. Nothing here ran on a macOS runner. `actionlint` does run on every pull request through `lint_workflows.sh`, so the YAML is checked, but shellcheck is absent from the runner and skipped, and no job on a pull request compiles iOS or macOS - the step's own last paragraph has said so since it was written. The first evidence is the next push to main. * Guard the pipe that aborted xcodebuild, and retire its five siblings The previous commit removed one `| head -n 1` and left a fourth prose warning about the shape. ADR 0004's argument is that a recurring mistake earns a ratchet instead, so this is the ratchet plus the rest of the instances. The guard goes in `lint_workflows.sh`, beside the `cond && 0 || 1` grep added for #292 - also added because nothing but a publish run could find that mistake. Same shape, same file, no new script and no new ci.yml step, so it runs on every pull request through the `actionlint` step that is already there. It reads the workflows, the composite actions and `.github/scripts`, and it catches more than one spelling: `| head`, `| grep -m`, and `| sed -n 1q`. Checked against a fixture of all four real pre-fix lines and three other early-exit spellings - all seven caught - and against prose quoting the forbidden shape, a `| cut | paste` chain, the correct `sed -n '1,60p'`, the `${v%%$'\n'*}` idiom, and a line of code whose trailing comment mentions `| head`: none of those match. `^[^#]*` is what keeps it off the prose, including the comment directly above it. Its bound is stated where it is written: it is textual, so a `while read … break` is the same trap with no keyword to grep, and the comment says to add a case rather than an exemption. The five remaining instances, all now pipe-free: - `build/{android,ios,linux,macos}.sh` each took the version from pubspec with `sed … | head -n 1`. The producer is a `sed` over a small file, so this was latent, not live - `derive_version.sh` calls it "a silent exit waiting for a longer file". Verified the extraction is unchanged against the real pubspec and against a synthetic one carrying build metadata and a second `version:` line: `0.17.3` and `1.2.3` either way. - `upload_r8_mapping.sh`'s `read_slug` captured through a herestring and then piped to `head`. Now captures and narrows in the shell. - `sentry_proguard_uuid.sh` ended `unzip | sed | head -n 1 || true`. The `|| true` stays, because `unzip -p` exits non-zero on an absent entry and that is one of the two absences this function answers with silence - but its comment claimed the `|| true` was there for the SIGPIPE, which is no longer a reason, so it now says which of the two it is. Gates: `upload_r8_mapping_test.sh`, `verify_apks_test.sh` and `derive_version_test.sh` all pass - the first two cover the two files with behaviour in them. `bash -n` on all seven. `check_format.sh` clean, `check_changelog.sh` OK, and the guard itself is green across `.github`. No changelog entry, for the same reason as the commit before it. Not verified on a runner, and the guard is the part that cannot be: it runs in `lint_workflows.sh`, which downloads a checksum-pinned `actionlint` before reaching it, so the first full execution is this pull request's own `lint-workflows` job. The grep was run directly here instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The dev build on
mainhas failed on macOS and iOS since d10fe8a (#492) —two runs red, and the merge before that one was the last green release run.
Nothing on a pull request could have caught it.
#492 moved
firebase_core4.11.0 → 4.15.2. Its podspec asks for a newerFirebase pod than the committed lock names, and
pod installkeeps a lockedversion for a transitive pod even when the development pod above it changed.
CocoaPods says so itself, in the second of the two causes it prints:
The error message blames the wrong thing, and I believed it first
Flutter appends this:
That is not a diagnosis.
cocoapods.dart's_diagnosePodInstallFailuredoesstdout.contains('out-of-date source repos'), and CocoaPods prints that phrasefor both causes it lists — so Flutter reprints the stale-index remedy
whichever one applies, then throws. It never retries with
--repo-update(
pod install --verboseis the only invocation in the file; the stringrepo-updatedoes not appear in it at all).The first version of this branch took that advice and added a
pod repo updatestep. The verbose log of run 43110342556 shows it would have been a no-op:
There is no baked index on the image to be stale — the trunk repo is created
during the run, and 693 podspecs are fetched fresh over the CDN with new
ETags. The exact version the resolver needed, 12.19.3, was downloaded and read
seconds before the failure. The
12.15.0in the error is labelledIn snapshot (Podfile.lock): it is our own committed file, which I had read as a ceiling inthe index.
pod install --repo-updatewould fail identically — refreshing sources doesnot unlock a pinned pod.
So the snapshot is the problem, and the snapshot cannot be maintained here
Committing
Podfile.lockis the usual advice, and it assumes somebody canregenerate it. In this project nobody can:
uname -s→exit 1),ci.ymlisanalyzeplus the BSD script tests and builds no Apple target,pod install.So a plugin bump invalidates two tracked files that no reviewer, no gate and no
local run can notice, and the first sign is a red release. That is t16
exactly: #492's own CI was green because nothing on a pull request compiles.
What is lost is narrow.
pubspec.lockstill pins every plugin to an exactversion, and each plugin's podspec pins the pods it needs — so what floats is a
third-party pod's patch range inside a
~>it already declared. What is gainedis a release that does not break on every plugin bump.
And it is reversible from real files rather than from a guess. The release
job now uploads the pair it resolved as
podfile-locks. Commit that pair anddrop the two
.gitignorelines, and the project is back to pinning — with alock that is actually correct, which is not true of the one in
maintoday.Why those steps are last in the job
Upload macOS and iOS release assetsabove it carries noif:, so it defaultsto
success(). A diagnostic step failing ahead of it would skip the DMG and IPAupload, skip
publish, and lose a release that was already built — the hazardthe Sentry step guards against the same way.
!cancelled()rather thansuccess()because a run where macOS resolved and iOS then failed is exactlywhen the resolved lock is worth having, and
if-no-files-found: warnratherthan
errorbecause a step whose only output is a diagnostic must not redden arelease.
Verified
ASSET_RETENTIONis defined at workflow level, so it is in scope here; theasset upload in the same job already uses the identical expression.
*.sh,*.dart,*.yml,*.md, excludinglib/modules/).with the new one last.
flutter_tools/lib/src/macos/cocoapods.dart(_runPodInstallline 352,_diagnosePodInstallFailureline 392, the reprinted advice line 410), notinferred from the console output.
not summarised.
What this does not prove: that the next release run goes green. Nothing
here can build iOS or macOS, so the first evidence will be the dev build after
this merges. If
pod installthen finds another pin it cannot satisfy, it willsay so with the same clarity.
Not in this PR
time, and closing it needs no Mac: after
flutter pub geton the Linuxanalyzejob every plugin's podspec is in~/.pub-cache, so a script canread each
s.dependencyrequirement and assert the lock satisfies it — thesame check
pod installperforms, and it would have failed on Update dependencies in waves, and hold the two that are not safe yet #492's owndiff. Moot while the locks are out of git, and the thing to build first if
they come back.