You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
firebase-tools: the install script served at https://firebase.tools, checked 2026-09-04 (installing 15.29.0). Reports of this go back to 15.0.0.
Platform: macOS 25.6 (reproduced here), also reported on Linux CI (CircleCI, Azure DevOps)
[REQUIRED] Test case
curl -sL https://firebase.tools | bash, run while the newest version on NPM does not yet have a downloadable GitHub release asset. That window opens on every release, so this is timing dependent rather than version specific.
[REQUIRED] Steps to reproduce
The mechanism, which needs no waiting. v99.99.99 stands in for any version whose asset is not downloadable yet:
curl -o /tmp/fb -L --progress-bar https://firebase.tools/bin/macos/v99.99.99
chmod +rx /tmp/fb && /tmp/fb --version
# /tmp/fb: line 1: Not: command not found
/bin/:os/:version 302s to the release asset on github.com, and a missing asset answers with a 9 byte body of Not Found, which is what ends up on disk.
To see the full script do it, run it with an isolated HOME and FIREBASE_BINARY so your real install is not touched, with the version pinned to one that has no assets:
A failed download aborts, and an existing install is left alone.
[REQUIRED] Actual behavior
The HTTP error body is installed as the binary and marked executable, so the CLI is broken until the user reinstalls:
-- Downloading binary from https://firebase.tools/bin/macos/v15.28.2
-- Setting permissions on binary... /usr/local/bin/firebase
/usr/local/bin/firebase: line 1: Not: command not found
Something went wrong, firebase has not been installed.
which firebase still resolves, and firebase --help keeps failing the same way, because the previous working binary was overwritten.
Root cause
Two things combine, and either one alone would be harmless.
1. A version is advertised on NPM before its binary is downloadable.
scripts/publish.sh:140-141 publishes to NPM.
scripts/publish/cloudbuild.yaml:114 then waits for the package to appear on NPM.
scripts/publish.sh:163-164 creates the GitHub release with hub release create --draft.
scripts/publish/cloudbuild.yaml:150-158 builds the standalone binaries, uploads them to that draft, and prints "Please review the draft release notes ... If it looks good, publish it".
So the assets only become publicly downloadable when someone publishes the draft by hand, which is after NPM already advertises the version. The install script takes its version from NPM (registry.npmjs.org/firebase-tools/latest) and downloads it from the release asset, so it asks for a version that is not there yet.
Measuring NPM publish time against max(release published_at, macos asset created_at) for 76 releases since January 2025:
every single one reached NPM before its binary was downloadable
median window 19 minutes, p90 2.1 hours
11 releases over 1 hour, longest 90.7 hours (v15.9.0)
v15.28.2 was 20.4 hours, v15.23.0 16.5 hours
(A further 21 releases are excluded because their asset created_at is more than an hour after the release was published, which I cannot tell apart from a delete and re-upload.)
2. The install script installs whatever curl left behind.
There is no --fail and the exit code is not checked, so an error response is saved, moved over $FIREBASE_BINARY and given chmod +rx. The --version check that notices runs after the move, which is why a working install is lost. This script is served from firebase.tools and is not in this repo.
The logs in #10991 and #10787 are the clearest evidence: both show the previous version installing successfully, then the new version failing minutes or hours later on the same machine.
#10031 is the same race with a different symptom. Before the script switched to a version pinned URL it used /bin/:os/latest, which resolves to the newest published release, so during v15.9.0's window it silently installed 15.8.0 while NPM said 15.9.0. #9607 shows /latest producing this exact Not: command not found failure too, so the underlying problem predates the versioned URL. The version pinned URL widened the exposure, because it has to wait for the release to be published rather than just for the upload to finish.
#10722 was closed after a missing v in the versioned URL was fixed, which was a real and separate bug. The window remains.
Related issues
Both halves of this have open issues already, so this is filed as the shared root cause rather than as another instance. If you would rather keep it in one place, closing this as a duplicate of either is completely fine.
Install script should abort on network failure #8931 asks for the script to abort instead of installing a failed download. That reporter hit a truncated transfer rather than a 404, but it is the same fail-open behaviour described in part 2 below.
The draft release looks deliberate, since the build asks for the notes to be reviewed before publishing, so the ordering is the thing to change rather than the review gate:
Pipeline (this repo, and the part [binary-publish-script] Enhance publish script reliability #7868 is closest to): publish to NPM last, after the release is published, so nothing advertises a version before its binary is downloadable. Reviewing the notes still gates the release.
Script (Install script should abort on network failure #8931): add --fail and check the exit code, so a failed download exits non-zero and leaves any existing binary in place. This does not close the window, but it turns a broken CLI into a retryable error. I have this patched and tested locally and am happy to send it if that helps, though the script is not in this repo.
Worth noting for anything that retries or falls back: pointing the script at /bin/:os/latest on failure would undo the NPM pin added in the same change, since latest follows GitHub rather than NPM.
What I verified how
Run, not read: the download and install behaviour, including the full script reproduction above, the Not Found body, and that an existing install is replaced. The release and NPM timestamps come from the GitHub and NPM APIs.
Read, not run: the pipeline ordering, from publish.sh and cloudbuild.yaml on main. I could not exercise a download against a genuinely unpublished draft release, so that step rests on the timestamps and the scripts rather than on an observed request.
[REQUIRED] Environment info
firebase-tools: the install script served at https://firebase.tools, checked 2026-09-04 (installing 15.29.0). Reports of this go back to 15.0.0.
Platform: macOS 25.6 (reproduced here), also reported on Linux CI (CircleCI, Azure DevOps)
[REQUIRED] Test case
curl -sL https://firebase.tools | bash, run while the newest version on NPM does not yet have a downloadable GitHub release asset. That window opens on every release, so this is timing dependent rather than version specific.[REQUIRED] Steps to reproduce
The mechanism, which needs no waiting.
v99.99.99stands in for any version whose asset is not downloadable yet:/bin/:os/:version302s to the release asset on github.com, and a missing asset answers with a 9 byte body ofNot Found, which is what ends up on disk.To see the full script do it, run it with an isolated
HOMEandFIREBASE_BINARYso your real install is not touched, with the version pinned to one that has no assets:[REQUIRED] Expected behavior
A failed download aborts, and an existing install is left alone.
[REQUIRED] Actual behavior
The HTTP error body is installed as the binary and marked executable, so the CLI is broken until the user reinstalls:
which firebasestill resolves, andfirebase --helpkeeps failing the same way, because the previous working binary was overwritten.Root cause
Two things combine, and either one alone would be harmless.
1. A version is advertised on NPM before its binary is downloadable.
scripts/publish.sh:140-141publishes to NPM.scripts/publish/cloudbuild.yaml:114then waits for the package to appear on NPM.scripts/publish.sh:163-164creates the GitHub release withhub release create --draft.scripts/publish/cloudbuild.yaml:150-158builds the standalone binaries, uploads them to that draft, and prints "Please review the draft release notes ... If it looks good, publish it".So the assets only become publicly downloadable when someone publishes the draft by hand, which is after NPM already advertises the version. The install script takes its version from NPM (
registry.npmjs.org/firebase-tools/latest) and downloads it from the release asset, so it asks for a version that is not there yet.Measuring NPM publish time against
max(release published_at, macos asset created_at)for 76 releases since January 2025:(A further 21 releases are excluded because their asset
created_atis more than an hour after the release was published, which I cannot tell apart from a delete and re-upload.)2. The install script installs whatever
curlleft behind.There is no
--failand the exit code is not checked, so an error response is saved, moved over$FIREBASE_BINARYand givenchmod +rx. The--versioncheck that notices runs after the move, which is why a working install is lost. This script is served from firebase.tools and is not in this repo.This is what the recurring install reports are
Four reports land inside a measured window:
curl -sL https://firebase.tools | bashinstalls 15.8.0 instead of 15.9.0 #10031 (15.9.0, inside a 90.7h window)curl -sL firebase.tools | bash#9607 (15.0.0, filed 53m after the NPM publish)The logs in #10991 and #10787 are the clearest evidence: both show the previous version installing successfully, then the new version failing minutes or hours later on the same machine.
#10031 is the same race with a different symptom. Before the script switched to a version pinned URL it used
/bin/:os/latest, which resolves to the newest published release, so during v15.9.0's window it silently installed 15.8.0 while NPM said 15.9.0. #9607 shows/latestproducing this exactNot: command not foundfailure too, so the underlying problem predates the versioned URL. The version pinned URL widened the exposure, because it has to wait for the release to be published rather than just for the upload to finish.#10722 was closed after a missing
vin the versioned URL was fixed, which was a real and separate bug. The window remains.Related issues
Both halves of this have open issues already, so this is filed as the shared root cause rather than as another instance. If you would rather keep it in one place, closing this as a duplicate of either is completely fine.
curl -sL firebase.tools | bash#9607,curl -sL https://firebase.tools | bashinstalls 15.8.0 instead of 15.9.0 #10031, Firebase tool 15.22.3 fail to install #10727, Firebase tool 15.23.0 fail to install #10787, Firebase tool 15.28.2 fail to install #10991, and [Install Failure] /usr/local/bin/firebase: line 1: Not: command not found #10722, which was closed after a missingvin the download URL was fixed. That was a real and separate bug, and the window described here is what remains.Suggested fix
The draft release looks deliberate, since the build asks for the notes to be reviewed before publishing, so the ordering is the thing to change rather than the review gate:
--failand check the exit code, so a failed download exits non-zero and leaves any existing binary in place. This does not close the window, but it turns a broken CLI into a retryable error. I have this patched and tested locally and am happy to send it if that helps, though the script is not in this repo.Worth noting for anything that retries or falls back: pointing the script at
/bin/:os/lateston failure would undo the NPM pin added in the same change, sincelatestfollows GitHub rather than NPM.What I verified how
Run, not read: the download and install behaviour, including the full script reproduction above, the
Not Foundbody, and that an existing install is replaced. The release and NPM timestamps come from the GitHub and NPM APIs.Read, not run: the pipeline ordering, from
publish.shandcloudbuild.yamlonmain. I could not exercise a download against a genuinely unpublished draft release, so that step rests on the timestamps and the scripts rather than on an observed request.