Android leaves ESR. It moves to Firefox release 157.0 now and follows Firefox release from here on, the same track as desktop. Decided 2026-10-02 by the maintainer, Manuel Gysin:
"we do the jump to 157 now and then follow mainstream from firefoxx, move fast and forward, its the timeage of ai."
(Quoted verbatim, spelling included.)
This supersedes the decision below (LW-M0-07, 2026-08-15). Everything from "Decision" down is the original analysis, kept unedited as the record of what was weighed. It is no longer the policy.
-
No ESR.
version.android=157.0andrelease.android=1. That is archive.mozilla.org's name for the release (releases/157.0/source/firefox-157.0.source.tar.xz, the tarball desktop already builds).make help TARGETS=androidshows157.0-1. Android and desktop now name the same Firefox version. The two-pair mechanism of §6 stays: Android'sversion.txtis still<version>-<release>(the shippedMOZ_APP_VERSIONcontract, seescripts/librewolf-patches.py), and the files let the tracks diverge for a release if a rebase lands on one target first. -
No
-esrcopies. The Firefox 157 desktop merge had parked eightpatches/android/*-esr.patchbyte copies,patches/android/pack_vs-esr.pyandl10n-esr/so that the Android tree stayed byte-identical on 153. They are deleted. Every patch now has one text for both targets, and the seven entries the merge had moved todesktop.txtare back incommon.txt. The accounting is inPATCH-SCOPE.md, at the top. -
A rebase every release. Android now rebases on each Firefox major (every two weeks since Firefox 155, §2) and on each dot release, together with desktop.
REBASE.mdstill describes an ESR dot rebase. Its mechanics (tarball,check-patchfail, pref audit, smoke) carry over unchanged. Its framing that a major comes once a year does not. -
Every Firefox release is flagged automatically. A rebase per release cannot depend on someone remembering to look.
.github/workflows/firefox-release-watch.yamlrunsscripts/firefox-release-watch.pyhourly on a GitHub-hosted runner: when product-details'LATEST_FIREFOX_VERSIONis newer thanversion.androidorversionand its source tarball is on archive.mozilla.org, it opens one issue,Firefox <v> released: rebase Android and desktop(labelfirefox-release), with the release notes, the advisories and theREBASE.mdsteps. The cadence and what the issue does not cover are inREBASE.md. Since LW-M7-47 (2026-10-09, after 157.0.1's issue #3 sat unseen for three days) the issue is assigned to the owner and mentions them. Mozilla's security advisories are matched against the shipped release. A dot release on the current major gets an unsigned candidate build:RELEASE-WATCH.md. -
Android-only Firefox for Android fixes now come from upstream. §3c's standing obligation goes away: someone had to read every "Firefox for Android" MFSA and backport by hand because no ESR
mobile/androidexisted. A release tarball carries Mozilla's ownmobile/androidfixes, and out-of-band Gecko security dots reach us the day Mozilla ships them, without §3a's ESR lag (median 14 days, worst 21). The LW-M7-05 statement drafted in §3 has to be rewritten for the release track (SECURITY.mdnow is). Checked 2026-10-04 (PARITY.md§7): the nine Firefox for Android CVEs open against the 153.4.0esr tree (REBASE.mdAppendix D) are all fixed in Firefox <= 157.0 and present in the 157.0 source; eight are closed by157.0-1(Beta 4 on), the ninth (CVE-2026-84135) is Focus-only and was never shipped. Mozilla can also ship an Android-only dot release, possibly without a desktop source tarball. Since 2026-10-04firefox-release-watchreadsmobile_versions.jsontoo and opens aFirefox for Android <v> released: rebase Androidissue for one (REBASE.md, "Release-track cadence"). -
The
MOZ_ESR-dependent sites of §1 and §5.3 need re-validation, above all the search-configuration channel (toolkit/components/search/SearchUtils.sys.mjs:357on 157:AppConstants.IS_ESR ? "esr" : AppConstants.MOZ_UPDATE_CHANNEL) and the Remote Settings channel (toolkit/modules/RustSharedRemoteSettingsService.sys.mjs:44, same expression). One correction to how §1 framed this: Redoubt's Android builds never actually hadMOZ_ESRset.version_display.txtwas153.0esr-1/153.4.0esr-1, which does not end inesr, sois_esrwas false (patches/android/build-fixes.patch, "MOZ_ESR is not set"). Both channels were already the update channel, and.jnlpwas already in the dangerous-extension list. What changes is the tree behind them: the search config dump (LW-M4-06), the Remote Settings collections and allowlist (LW-M4-08) and--enable-appservices-in-tree(M2) now come from a release tree that moves every two weeks. Re-validate them on 157, then at every rebase. -
Settings.
android.cfgno longer restates the 153-era values that the Firefox 157 settings port had taken out ofcommon.cfgon Android's behalf. Android takescommon.cfg, apart from its own decisions (Redoubt-settings, branchandroid-release-157). -
Cookie banners: the Firefox service is gone, uBlock Origin's lists replace it. Firefox 156 removed the cookie banner service: 157 has no
toolkit/components/cookiebanners, nocookie-banner-rules-listdump, nocookiebanners.*prefs, and GeckoView's Cookie Banner Handling API is gone ("there is no replacement", bug 2058143). The 153 betas carried the service with packaged rules (LW-M7-13) and controls (LW-M7-23); both patches are retired, and LW-M7-13, -22 and -23 with them. Owner decision 2026-10-02: uBlock Origin's cookie-notice lists take over, on by default, opt-out in uBO's own "Filter lists" pane like any other list.settings/android.cfgpointslibrewolf.uBO.assetsBootstrapLocationatassets/uBOAssets.android.json, LibreWolf's uBO catalog with exactly two lists switched on (fanboy-cookiemonsterandublock-cookies-easylist, uBO's "EasyList/uBO – Cookie Notices" group) and no self-update URL (scripts/gen-ubo-assets-android.py, LW-M7-41). This is not the same thing: the Firefox service clicked a site's own "reject" button, so the site recorded a refusal. A filter list hides the banner or blocks its script, so the site may record nothing at all, and a site that requires an answer may break until the list is turned off for it. Until the catalog URL resolves, uBO falls back to the catalog in its own XPI (stock defaults, cookie lists off).Existing profiles get the lists once (owner decision for Beta 5). A profile that ran Beta 3 or earlier keeps its uBO catalog and selection, and an offline first run keeps the XPI's; neither ever saw the lists on.
patches/android/ubo-cookie-lists-migration.patchturns them on one time: it hooks the storage backend selection that uBO's firststorage.localcall of each start waits on, so it acts before uBO reads its selection (while the migration is pending, startup leaves uBO's backend unannounced so that call really goes through it; upstream announces it for every migrated extension, which made the first version of the hook dead code). IfselectedFilterListsnames neither list, both are appended (nothing else changes) and, once that write committed,librewolf.uBO.cookieListsMigratedis set; from then on the hook does nothing, so turning the lists off sticks. A selection that already names one or both is taken as the user's choice and only sets the pref. A fresh profile is not written to: its first selection is inspected when uBO saves it, and only an offline first run (neither list) is migrated, on the next start. uBO's managed storage was not used: it re-applies at every start and replaces the whole selection. On the start that adds them, the lists show ticked at once and filter after uBO's own updater fetches them (seconds, with network and auto-update on; otherwise at the next update or list change). Residual: a user who had both on and turned both off before this ran (a Beta 4 install that got the Android catalog) sees them on once more. Verified on an emulator with the Beta 5 candidate (evidence/lw-m7-41/migration/): Beta 4 offline and Beta 3 profiles get both once, active on that first start, and an opt-out survives restarts and a reinstall; a fresh install is a no-op.The catalog URL is pinned to a commit (owner decision 2026-10-02: a branch URL is a mutable trust anchor). uBO 1.75.0 reads the bootstrap location only when it has no catalog yet; afterwards it refreshes the catalog every 13 days from the catalog's own
assets.jsonentry, so that entry is the long-term anchor. It cannot name the pinned commit (a file cannot carry the hash of the commit that contains it), a branch is what the owner rejected, and upstream's or LibreWolf's catalog keep the cookie lists "off", which on refresh makes uBO drop them from the user's selection (js/storage.js,assets.json-updated). So the Android catalog'sassets.jsonentry has"contentURL": []: uBO never refreshes the catalog and keeps the one it bootstrapped. The filter lists themselves keep updating from their upstream URLs. Cost: a catalog change reaches fresh installs only.Re-pinning, whenever
assets/uBOAssets.android.jsonchanges (ascripts/update-ubo-assets.shrun, a generator change):- commit the regenerated catalog (commit A) -- nothing else needs to be in
it, and it must reach
CPlusPlus17/Redoubtunchanged (no rebase, no squash), because its hash is the URL; - set
CATALOG_COMMITinscripts/gen-ubo-assets-android.pyto A's full hash, andlibrewolf.uBO.assetsBootstrapLocationin Redoubt-settings'android.cfgtohttps://raw.githubusercontent.com/CPlusPlus17/Redoubt/<A>/assets/uBOAssets.android.json; commit settings, then the gitlink and the generator together (commit B).scripts/tests/test-ubo-cookie-lists.pyfails between the two steps (the pinned commit no longer serves the current catalog, or the cfg and the generator disagree): that is the reminder. Current pin:612fac026e238ebac8745b6b9ed0922790473f21(Redoubt-settings8a69936).
- commit the regenerated catalog (commit A) -- nothing else needs to be in
it, and it must reach
-
Four 157 features that send browsing data to Mozilla or Google are off for good (owner decision 2026-10-02,
patches/android/disable-157-cloud-features.patch, LW-M7-40): Shake to Summarize (page text to Mozilla's MLPA service), IP Protection (Mozilla's hosted proxy, never initialised), the "Add shortcut" sheet's Merino image-CDN icons, and the Google Lens image upload. Each switch is a constant, not a default. -
Local Network Access on top-level navigations stays on. The settings move to 157 had let Android follow upstream on
network.lna.allow_top_level_navigation(157 defaults it to true; LibreWolf stopped setting it false because OAuth flows to local addresses broke). Owner decision the same day: restored to false insettings/android.cfg, as the betas shipped. Desktop follows upstream. -
The navigation storage-access heuristic stays off (owner decision 2026-10-04).
privacy.restrict3rdpartystorage.heuristic.navigationgrants unpartitioned third-party storage to sites the user interacted with during a navigation that returns to the starting site. 157 ships it false on Android only (StaticPrefList.yaml:18242-18249); 158 ships it true everywhere (158.0b3:18387-18390, found in the 158 pre-rebase StaticPref diff).settings/android.cfgpins it false (Redoubt-settings990c367) ahead of the 158 rebase, so the rebase does not turn it on. Desktop follows upstream. -
The recently-visited storage-access heuristic is pinned off (owner decision 2026-10-04).
privacy.restrict3rdpartystorage.heuristic.recently_visitedgrants a redirect-through tracker storage access when it was recently visited as a first party. 157 ships it true on Android (StaticPrefList.yaml:18296-18303); 158 ships it false everywhere (158.0b3:18437-18440).settings/android.cfgpins it false (Redoubt-settings008b87f), so a later upstream flip back cannot turn it on. On 158 this is a no-op. Desktop follows upstream.
The original analysis argued against this move. It now applies as a cost estimate, not as a veto.
- §2: 26 hard rebases a year instead of 1 (two-week cadence since 2026-09-01), about 46 releases a year in total. Each major means re-deriving the patch set, the Fenix Kotlin removals and the pref delivery against a moved tree. The analysis called this "where the port dies" for a one-person team. The maintainer's answer is to accept the per-release cost and automate it ("move fast and forward").
- §5.1: the jump is paid in one go. 153.4.0esr → 157.0 spans four majors
(154 to 157) with no intermediate green build to bisect against. When the
decision was taken,
check-patchfail --targets=androidon 157.0 failed 29 of the then 44 Android patches. That port is done: three patches were retired because 157 has nothing left for them to patch (r8-keep-rules,cookie-banner-rules,cookie-banner-controls;PATCH-SCOPE.md), the other 41 were rebased and apply at fuzz 0 in list order, anddisable-157-cloud-featureswas added, soandroid.txthas 42 entries andcheck-patchfail --targets=androidand--targets=desktopboth pass on 157.0. Applying is not building. A first scratch x86_64 APK was linked on 2026-10-02 only after local workarounds for several 157 build defects in the patcher, the pregenerated UniFFI bindings and three patches (docs/android/evidence/lw-m7-40/README.md); a clean 157 build is the next gate. - §4: Tor Browser stops being a co-maintainer. Tor Browser Stable stays on ESR, and its Alpha is on rapid release. Nobody shares our exact base any more.
- §5.4: users see a version jump, 153.4.0esr-1 → 157.0-1. This is cosmetic.
The Android versionCode is unaffected:
deterministic-version-code.patchderives it fromMOZ_BUILD_DATE, not from the version string. - §5.6 still holds, and is why this can be reversed: the track was never
coupled to the
applicationIdor the signing key, so the move needs no new identity and no re-install.
SUPERSEDED 2026-10-02. Everything below is the original LW-M0-07 analysis and decision. It is kept for the record and is no longer the policy.
Task: LW-M0-07 · Status: decided, pending maintainer sign-off (see the last section) · Written: 2026-08-15 against the stock Firefox 153.0.4 tree and the Mozilla release calendar as of that date.
Redoubt tracks Firefox ESR. LibreWolf desktop stays on Firefox
release. The Android track starts at esr153 (version.android = 153.0esr,
release.android = 1) and follows the ESR series — dot releases as they ship,
one major rebase a year when Mozilla cuts the next ESR.
This upholds the recommendation on record in
ROADMAP.md, but three of the four numbers in that
table are wrong and one of them is wrong in a way that matters. They are corrected
below. The decision survives the correction — it survives it more strongly than
the roadmap argued, because the release track got twice as expensive in July 2026.
The roadmap says the adoption cost is "zero, esr153 is the tree in the current tarball". That is false. The current tarball is the release channel.
Evidence, from the stock tree extracted at firefox-153.0.4/:
| file | contents |
|---|---|
browser/config/version.txt |
153.0.4 |
browser/config/version_display.txt |
153.0.4 — no esr suffix |
config/milestone.txt |
153.0.4 |
build/moz.configure/init.configure:1151 computes the channel from exactly that
string:
is_esr=app_version_display.endswith("esr") or None,and :1179-1180 turn it into MOZ_ESR. With version_display.txt = 153.0.4,
MOZ_ESR is unset in this tree. The Makefile fetched it from
archive.mozilla.org/pub/firefox/releases/153.0.4/source/, the release-channel
path. ./version says 153.0.4, and 153.0.4 is a release-channel version number;
an ESR one would read 153.0esr or 153.1.0esr.
What is true: 153 is the version Mozilla cut ESR from, so the roadmap's
instinct was right even though its claim was not. The same tree proves it —
taskcluster/config.yml:19-25 lists esr153 as a live release branch next to
esr140 and esr115, and taskcluster/kinds/merge-automation/kind.yml:44-89 has
release-to-esr: firefox-esr153 and bump-esr153 targets. ESR 153 shipped
2026-07-21 as firefox-153.0esr.source.tar.xz (767 M) at
archive.mozilla.org/pub/firefox/releases/153.0esr/source/ — verified to exist; a
different artifact of almost exactly the same size as the 153.0.4 release tarball
(766 MiB), because esr153 branched from 153.0 and has not diverged much yet.
MOZ_ESR is not inert. In this tree it changes:
| site | effect on ESR |
|---|---|
xpcom/io/nsLocalFileCommon.cpp:72 |
.jnlp is not in the dangerous-extension list |
toolkit/components/reputationservice/ApplicationReputation.cpp:279 |
same, for download reputation |
toolkit/components/search/SearchUtils.sys.mjs:361 |
search configuration channel becomes "esr" |
toolkit/modules/RustSharedRemoteSettingsService.sys.mjs:44 |
Remote Settings channel becomes "esr" |
toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs:594 |
the install is treated as enterprise |
toolkit/components/backgroundtasks/defaults/backgroundtasks.js:61 |
background-update throttling default flipped |
browser/branding/official/pref/firefox-branding.js:24 |
ESR update / release-note URLs |
Two of those land on work already on the board: the search-configuration channel
affects M4's search work and M2's --enable-appservices-in-tree, and the
.jnlp difference is a download-handling behaviour change that the M5 parity
matrix should have a row for. The policy-engine one is Android-irrelevant (the
engine is compiled out — see ROADMAP.md, "What makes this hard") but matters if
desktop is ever asked to follow.
Adoption cost today is therefore near zero for the patch set and not zero for
the build. The 53 patches apply to both trees because both are Firefox 153; but
adopting esr153 means a second 767 MB tarball, a second ~10 GB extracted tree on
every builder and on the Forgejo epsilon runner, and the plumbing in this task.
Say "one tarball, no patch rebase" — not "zero".
- F1 — Mozilla's public release calendar, https://whattrainisitnow.com/calendar/, read 2026-08-15.
- F2 — the Mozilla Foundation Security Advisory index, https://www.mozilla.org/en-US/security/advisories/, read 2026-08-15. Every advisory carries a date and a product/version, so this is the ground truth for "how many times did each channel actually have to ship".
- F3 — the
dev-platformannouncement of the cadence change, 2026-07-14/17, and its coverage (The Register, gHacks, OMG!Ubuntu, Linuxiac). - F4 — https://endoflife.date/firefox, read 2026-08-15, for ESR support windows. Treat with care: it lists "153.0.4" as the latest ESR 153 version, which is the release-channel number — it makes the same conflation the roadmap made.
Firefox moves to a two-week major release cadence starting with Firefox 155 on 2026-09-01 (F3). F1 confirms it: 155 Sep 1, 156 Sep 15, 157 Sep 29, 158 Oct 13, 159 Oct 27, 160 Nov 10, 161 Nov 24, 162 Dec 8, 163 Jan 12 2027, 164 Jan 26 2027 — exactly 14 days apart. That is 26 majors a year. The roadmap's "26" is right, and it is right for a reason that became public roughly a month before the roadmap was written: until 2026-08-18 the observed rate was 4-weekly (7 majors, 147 on Jan 13 → 153 on Jul 21 = 189 days, a 27-day mean, ~13.5/year, F2).
| desktop — release | android — esr153 | |
|---|---|---|
| majors per year | 26 from 2026-09-01 (F1, F3); 13.5 observed before it (F2) | 1 |
| dot releases per year | ~20 — 11 out-of-band security releases in the 204 days 2026-01-13 → 2026-08-04 (F2) | ~21 observed, ~26 scheduled — see below |
| total releases we must cut per year | ~46 | ~27 |
| hard rebases (patches break, Fenix churns) | 26 | 1 |
| adoption cost today | — | one 767 MB tarball, one extra ~10 GB tree; no patch rebase |
| co-maintainer of the same base | — | Tor Browser Stable (verified, §4) |
The roadmap's "~12 dot releases per year" for ESR is wrong by about 2×. ESR 140 shipped 11 security releases in the 189 days from 2026-01-13 to 2026-07-21 (140.7, 140.7.1, 140.8, 140.9, 140.9.1, 140.10, 140.10.1, 140.10.2, 140.11, 140.12, 140.13 — F2), an annualised ~21. Under the new cadence it gets worse: F1 schedules an ESR 153 point release on every release date — 153.1 Aug 18, 153.2 Sep 1, 153.3 Sep 15, 153.4 Sep 29, 153.5 Oct 13, 153.6 Oct 27, 153.7 Nov 10, 153.8 Nov 24, 153.9 Dec 8, 153.10 Jan 12, 153.11 Jan 26 — ~26 a year.
Choosing ESR does not meaningfully reduce the number of releases we cut (~46 → ~27, a factor of 1.7). It reduces the number of releases where the 53-patch set, the three-way patch split, the pref delivery and the eleven M4 Kotlin removals all have to be re-derived against a moved tree: 26 a year → 1 a year.
That is the entire argument, and it is stronger than the roadmap's version. The
per-release cost of a dot rebase is a tarball fetch, check-patchfail, the pref
audit and the smoke test — automatable, and LW-M7-01 automates it. The per-release
cost of a major rebase is a person. Twenty-six of those a year, on top of desktop's
twenty-six, is where the port dies.
This is the strongest argument against ESR for a browser that markets itself on security, the roadmap does not quantify it, and LW-M7-05 has to publish a turnaround target against it. There are three distinct latencies.
Mozilla ships an ESR point release alongside each major, and sometimes alongside an out-of-band release-channel fix. From F2, every release-channel out-of-band security release in 2026 and whether ESR 140 got one the same day:
| release-channel fix | date | same-day ESR? | ESR latency |
|---|---|---|---|
| Firefox 147.0.2 (MFSA 2026-06) | Jan 27 | no | ESR 140.7.1, Feb 16 → 20 d |
| Firefox 147.0.4 (MFSA 2026-10) | Feb 16 | yes — same advisory as ESR 140.7.1 / 115.32.1 | 0 d |
| Firefox 148.0.2 (MFSA 2026-19) | Mar 10 | no | ESR 140.9, Mar 24 → 14 d |
| Firefox 149.0.2 (MFSA 2026-25) | Apr 7 | yes — ESR 140.9.1 (MFSA 2026-27) | 0 d |
| Firefox 150.0.1 (MFSA 2026-35) | Apr 28 | yes — ESR 140.10.1 (MFSA 2026-36) | 0 d |
| Firefox 150.0.2 (MFSA 2026-40) | May 7 | yes — ESR 140.10.2 (MFSA 2026-41) | 0 d |
| Firefox 150.0.3 (MFSA 2026-45) | May 12 | no | ESR 140.11, May 19 → 7 d |
| Firefox 151.0.3 (MFSA 2026-54) | Jun 2 | no | ESR 140.12, Jun 16 → 14 d |
| Firefox 152.0.4 (MFSA 2026-62) | Jun 30 | no | ESR 140.13, Jul 21 → 21 d |
| Firefox 152.0.6 (MFSA 2026-67) | Jul 14 | no | ESR 140.13, Jul 21 → 7 d |
Six of ten had no same-day ESR counterpart. For those six: median 14 days, mean 13.8, maximum 21.
The worst case is verified end to end and is the one to quote. MFSA 2026-67, Firefox 152.0.6, 2026-07-14, impact Critical, fixing CVE-2026-15718 (invalid pointer in JavaScript: WebAssembly) and CVE-2026-15719 (site isolation in DOM: Navigation), both annotated "exploit code for this is public however we are not aware of any attacks in the wild". Both CVEs first appear on the ESR side in MFSA 2026-70, Firefox ESR 140.13, 2026-07-21 — seven days later. For seven days an ESR user was exposed to two Critical bugs with public exploit code that a release user was not.
Mozilla's ESR policy backports Critical and High and leaves much of the rest. On 2026-07-21 Firefox 153 (MFSA 2026-68) listed 63 CVEs; the same-day ESR 140.13 advisory (MFSA 2026-70) listed 32, of which 29 overlap. 34 of the 63 CVEs fixed in Firefox 153 had no ESR fix that day.
Caveat, stated because it is load-bearing: an unknown fraction of those 34 do not affect ESR 140 at all — the code was written after 140 branched. So 34 is an upper bound on the gap, not a count of live unpatched bugs. The direction is not in doubt; the magnitude is uncertain, and nobody outside Mozilla can resolve it from public data. What is certain is that the residue waits for the next ESR major, i.e. up to ~13 months.
Firefox for Android ships from Nightly, Beta and Release GeckoView. There is no
ESR channel — the last time one existed was Fennec on ESR 68 in 2019. Mozilla
therefore has no product built from mozilla-esr153's mobile/android, and no
reason to uplift Android-layer fixes to it.
This is not hypothetical. MFSA 2026-73, 2026-08-04, impact High, "Security Vulnerabilities fixed in Firefox for Android 153.0.3", CVE-2026-18809, an information disclosure affecting Firefox for Android and Firefox Focus for Android. It is Android-only, it names no ESR version, and as of 2026-08-15 no ESR release has carried it. The next scheduled ESR 153 point release is 153.1 on 2026-08-18 — 14 days after the release-channel fix, and whether it carries this CVE at all depends on whether the bug is in Gecko or in the Kotlin layer.
(Superseded 2026-10-02: on the release track these fixes arrive with the
rebase. Outcome for the nine that were open against 153.4.0esr: PARITY.md §7.)
Redoubt on ESR inherits this gap and must close it by hand. That is a standing obligation, not a one-off: someone reads every "Firefox for Android" MFSA and decides per advisory whether to backport into our tree.
It is the strongest argument against this track, and it does not overturn it — it converts into a named piece of work. Twenty-six hard rebases a year is not survivable by this team. One advisory stream to watch is.
Not a number we cannot hit. The honest statement:
A Gecko security fix that Mozilla ships out of band to Firefox release reaches Redoubt when it reaches Firefox ESR — historically the same day for roughly half of them, and a median of 14 days (worst observed 21 days) for the rest. LibreWolf's own turnaround on top of that is days from the ESR tarball to a published APK. Android-only advisories with no ESR counterpart are triaged individually and we publish the outcome.
<N> is ours to set and ours to meet; the rest is Mozilla's cadence and we should
say so rather than absorb it into a single number that looks like a promise.
The roadmap claims Tor Browser as a co-maintainer of the same base. Checked:
- Tor Browser Stable 15.0 is built on Firefox ESR 140 — desktop and Android.
Tor Browser 15.0.11 (2026-04-28) shipped with GeckoView bumped to
140.10.1esr, i.e. Tor Browser for Android is built from the ESR branch'smobile/android. That is precisely the configuration Redoubt would be. - Tor Browser Stable 16.0 will be based on Firefox ESR 153, expected mid-Q3 2026; alpha 16.0a9 (2026-07) already runs on esr153. There is no 15.5 — they go straight from ESR 140 to ESR 153.
- So they are on the same base, on the same platform, and they got there first. If Fenix-from-an-ESR-branch did not build, they would have found out before us. This is genuine co-maintenance, not coincidence.
One qualification the roadmap does not have. On 2025-12-01 the Tor Project changed its model ("The Future of Tor Browser Alpha"): from 16.0a1, Tor Browser Alpha is based on Firefox Rapid Release, not ESR. Only Stable remains on ESR. Their stated reasons are the ones we are relying on in reverse — cascading delays, developer stress in the compressed ESR transition window, and poor feature continuity.
Two consequences for us. First, the ESR base is no longer where all of Tor's attention is, so the co-maintenance benefit is real but eroding; re-check it at each ESR transition rather than assuming it. Second, they are now the closest thing to a controlled experiment on exactly our question, and their answer was to keep the shipping product on ESR and take the rapid-release pain only in an alpha they do not have to support.
ESR 140 reaches end of life on 2026-09-29 (F4, and F1 shows 140.17 on Sep 29 as its last scheduled point release). ESR 153 is the only forward-looking ESR from that date. Adopting esr153 now is adopting the current ESR, not one about to expire.
If Android ships on ESR and we later want release:
- The version jump is the cost, and it compounds. Switching means jumping from esr153 to whatever release is current, all at once. At 26 majors a year, a switch six months in is a ~13-major jump with no intermediate green build to bisect against. That is strictly worse than 13 incremental rebases would have been. The reversal cost grows linearly with how long we stay on ESR — this is the one property that makes the decision feel one-way, and it is a schedule cost, not an architectural one.
- Nothing in the patch set or the build encodes ESR.
version.androidis one line. The plumbing in §6 is version-agnostic.MOZ_ESRchanges the behaviour of the seven sites in §1 and nothing we wrote. - Two of those seven need re-validation on a switch. The search-configuration
channel (
SearchUtils.sys.mjs:361) and the Remote Settings channel (RustSharedRemoteSettingsService.sys.mjs:44) both flip between"esr"and the update channel. M4's search work and M2's--enable-appservices-in-treemust be validated against whichever we ship, and re-validated if we switch. - Users see a version discontinuity (153.x → 16x.x). Cosmetic on its own.
- The asymmetry matters: release → ESR later is cheap (wait for the next ESR cut, land on it, one ordinary major rebase). ESR → release later is expensive (1). So "start on release, switch to ESR later" is the cheap-to-reverse option and it is the honest counter-argument to this decision. It is outweighed by the 328–638 hours it takes to reach an APK: on the release track the tree the port is being built against would move under it ~26 times during development, and M4's eleven Kotlin tasks would be rebased across every one of them.
- What would make it genuinely irreversible: coupling the track to LW-M6-01's
applicationIdand signing key. A newapplicationIdhas no upgrade path from any existing install and the key cannot be replaced. Nothing about the track requires touching either — keep them uncoupled, and this stays a schedule decision.
The identity decision §5 item 6 keeps uncoupled from the ESR track is made and is
recorded here per LW-M4-07 acceptance #1. The authoritative record is
IDENTITY.md; this is the track's copy so the decision is visible in the
document that gates release.
applicationId org.redoubtbrowser
domain redoubtbrowser.org (decided 2026-08-23, not yet registered)
Decided and signed off 2026-08-23 (see IDENTITY.md for the full record, including
the libreRedoubt rejection). One-way: a changed applicationId is a different
app to Android — no upgrade path, no data migration — and F-Droid and Accrescent
key on it. Channel suffixes are the clean two-segment org.redoubtbrowser.beta
and org.redoubtbrowser.debug; the three-segment alternative
org.redoubtbrowser.android was rejected.
State at recording: decided, not yet applied. The build still ships as
org.mozilla + .fenix.debug — Mozilla's namespace, which collides with a real
Firefox install on a device. LW-M4-07 owns applying it, and "applying it" is
wider than the manifest:
build.gradleapplicationId(currentlyorg.mozilla) →org.redoubtbrowser, plus the per-variant suffixes (.fenix.debug,.firefox,.firefox_beta→.debug, none,.beta). Thenamespace(org.mozilla.fenix, the Kotlin package) stays — it is not branding and renaming it breaks every patch.deepLinkScheme(currentlyfenix/fenix-beta/fenix-dev) — a separate manifest placeholder, not derived from applicationId; must be re-decided.sharedUserId "org.mozilla.firefox.sharedID"(currently on beta + release) is Mozilla's and must be dropped.- The user-visible name, icons (brand colour
#00acff) and about screen. - The coupled script references in
scripts/android-*.shthat hardcode the old identity.
This is a recording pointer, not a build change, and it does not alter the ESR decision above.
$(lw_source_dir) is librewolf-$(version)-$(release). One extracted tree cannot
be two Firefox versions at once. So what happens when desktop wants 153.0.4 and
Android wants 153.0esr?
Yes — and the reason is timing, not symmetry. If version.android merely mirrored
./version, the mechanism would be an empty maintenance cost and should not be
built. It does not mirror it: the tracks diverge today, because
archive.mozilla.org serves 153.0.4 and 153.0esr as two different tarballs, and
they diverge further every fortnight from 2026-09-01.
The alternative — do M1–M5 against the shared 153.0.4 tree and switch to ESR at M6
— looks cheaper and is not. M4's eleven de-Mozilla-ing tasks are done against
mobile/android source. Done against release-Firefox-160-ish and then moved to
esr153, that is a backport across ~13 majors, which is harder than the forward
port it replaces. Doing them against the tree we intend to ship is the cheap order.
This is the one question in §8 where the maintainer could reasonably decide the
other way, so it is called out explicitly there.
./version + ./release name the desktop track. ./version.android +
./release.android name the Android track. TARGETS selects which pair is in
effect for one make invocation:
TARGETS |
version pair in effect |
|---|---|
desktop (default) |
./version, ./release — unchanged from before this task |
contains android, not desktop |
./version.android, ./release.android |
| contains both | ./version, ./release, plus a guard that refuses to build (below) |
Because everything downstream is already written in terms of $(version) and
$(release), overriding those two variables is the whole mechanism:
$(ff_source_tarball)→firefox-153.0esr.source.tar.xz$(ff_source_url)→.../releases/153.0esr/source/firefox-153.0esr.source.tar.xz$(ff_source_dir)→firefox-153.0esr$(lw_source_dir)→librewolf-153.0esr-1$(lw_source_tarball)→librewolf-153.0esr-1.source.tar.gz
The two tracks therefore get separate tarballs, separate extracted trees and
separate output archives for free, and an Android build never touches the desktop
tree. No new FF_CHANNEL case is needed: ESR tarballs live under the same
releases/<version>/source/firefox-<version>.source.tar.xz layout — 153.0esr is
just a version string to archive.mozilla.org. Verified against the live listing.
$(version_files) follows the same switch, so a desktop version bump does not
invalidate the Android tree or vice versa.
Once the tracks diverge there is no single tree to apply both platform patch sets
to. The Makefile refuses, via $(lw_tree_guard), which is:
- empty when the tracks agree or only one platform is requested — and make
drops a recipe line that expands to nothing entirely, so the default
make -n diroutput is byte-identical to what LW-M0-01 left; - a recipe-time
exit 1otherwise, not a parse-time$(error), somake -nstays a real dry run and LW-M0-01's own check,make -n dir TARGETS=desktop,android | grep -- '--targets=desktop,android', still passes.
TARGETS=desktop,android thus remains a valid plumbing check and stops being a
valid build. TARGETS=common,android — the combination ROADMAP.md's M0 exit
criterion names — resolves to the Android track and works.
targets_stamp is librewolf-targets-$(subst $(comma),-,$(TARGETS)) and its recipe does
rm -f librewolf-targets-*. It records which patch sets a tree was built with,
but not which version — and with two coexisting trees that is not enough.
Alternating make dir and make dir TARGETS=android deletes the other track's
stamp each time, so every switch re-extracts a tree that was already correct:
~10 GB and tens of minutes of churn per switch.
Not fixed here, deliberately. The fix renames the stamp, which changes
make -n dir output and forces one re-extract for every existing desktop builder —
neither is acceptable inside a task whose job is to leave desktop alone. The fix
itself is two lines, for whoever owns the Makefile next (LW-M0-02, or a new M0
task):
targets_stamp := librewolf-targets-$(version)-$(release)-$(subst $(comma),-,$(TARGETS))
# and in its recipe:
# @rm -f librewolf-targets-$(version)-$(release)-*make check runs scripts/update-version.py, which writes ./version only.
version.android is deliberately not bumped automatically — an ESR dot release
should be adopted by a human who has read the advisory. But there is no
make check-android either, so nothing tells the maintainer that 153.1esr shipped.
That belongs with LW-M7-01's rebase runbook and LW-M7-05's turnaround target; it is
not plumbing this task can add without owning scripts/.
Two tracks means two extracted trees, ~10 GB each, plus two ~770 MB tarballs, on
every builder and on the Forgejo epsilon runner. LW-M0-06 (toolchain image) and
the CI skeleton need to budget for it.
This task does not own these files. Recorded here so they are not lost.
| where | says | should say |
|---|---|---|
ROADMAP.md "Release track" table |
"adoption cost today — zero, esr153 is the tree in the current tarball" | the current tarball is release-channel 153.0.4; adopting esr153 costs one extra tarball and one extra tree, and no patch rebase |
ROADMAP.md "Release track" table |
android (esr) "~12" dot releases per year | ~21 observed for ESR 140 in 2026, ~26 scheduled for esr153 under the two-week cadence |
ROADMAP.md "Release track" table |
desktop "26 majors per year" | correct — but note it becomes 26 only on 2026-09-01 (Firefox 155); it was 13.5 when the roadmap was written |
ROADMAP.md M0 exit |
"make dir TARGETS=common,android produces a tree" |
say which tree — it is now the esr153 tree, not the desktop one |
tasks.yaml LW-M0-07 what: |
repeats "zero adoption cost … esr153 is the tree in the current tarball" and "~12 dots" | as above |
Makefile:44 and Makefile:62 |
ff_source_tarball is assigned twice; the second drops FF_BETA_SUFFIX |
pre-existing, unrelated to this task, but it means FF_BETA_SUFFIX has no effect on the tarball name |
An agent cannot sign this off — LW-M0-07's verify is
manual: maintainer sign-off recorded in TRACK.md. The block below is what is
being agreed to. Nothing here is binding until it is filled in.
By signing, the maintainer agrees to all of:
- The track. Redoubt tracks Firefox ESR, starting at esr153; LibreWolf desktop stays on Firefox release. One Android major rebase a year, ~26 dot rebases a year (§2).
- The published latency. Redoubt will be behind LibreWolf desktop on out-of-band Gecko security fixes by a median of 14 days and up to 21 days, and LW-M7-05 will publish that rather than a number we cannot hit (§3a, §3-final).
- The watch obligation. Because there is no Firefox for Android ESR, a named person reads every "Firefox for Android" MFSA and decides per advisory whether to backport into our tree (§3c). This is recurring work not currently budgeted anywhere on the board.
- The extra tree. Every builder and the CI runner carry a second ~10 GB extracted tree and a second ~770 MB tarball from now on (§6).
TARGETS=desktop,androidis no longer a buildable combination — it remains a valid dry-run plumbing check (§6).- The track stays uncoupled from LW-M6-01. The
applicationIdand the signing key are decided on their own merits; nothing about ESR-vs-release may be used to justify either (§5.6).
Questions the maintainer must answer, not just tick:
- Q1 — switch now or at M6?
version.androidis set to153.0esrtoday, which means M1–M5 do their work against the ESR tree. The alternative is to leave it at153.0.4, do the port against the shared tree, and switch at M6. §6 argues for now (M4's Kotlin work would otherwise become a ~13-major backport); the cost is a second 10 GB tree from M1 onward. Which? - Q2 — who owns the Android advisory watch (§3c), and with what response time? If the answer is "nobody", the honest move is to say so on the download page, because it is a real difference from desktop LibreWolf.
- Q3 — what is
<N>, our own turnaround from an ESR tarball to a published APK across our own F-Droid repo, Accrescent and direct APK (§3-final)? LW-M7-05 cannot be written without it. - Q4 — do we accept the erosion of the Tor co-maintenance argument? Tor Browser Alpha left ESR in Dec 2025; only Stable remains (§4). Should this decision be re-examined at each ESR transition, and by what trigger?
- Q5 — may the
ROADMAP.mdandtasks.yamlcorrections in §7 be landed, and by whom? This task does not own those files. - Q6 — who fixes the
targets_stampdefect in §6, and does it go into LW-M0-02 or a new M0 task?
Decision: Android tracks Firefox ESR (esr153); desktop stays on release
Recorded by: LW-M0-07 (agent), 2026-08-15
Evidence reviewed: sections 1-5 of this document
Maintainer: ______________________________
Date: ______________________________
Agreed items 1-6: [ ] yes [ ] with the exceptions noted below
Q1 answer: ______________________________
Q2 answer: ______________________________
Q3 answer (N days): ______________________________
Q4 answer: ______________________________
Q5 answer: ______________________________
Q6 answer: ______________________________
Exceptions / notes:
Every figure above comes from a public source that can be re-read at the next ESR transition. Do that rather than trusting this file a year from now.
- Cadence: https://whattrainisitnow.com/calendar/ — the "Matching ESR" column gives the ESR point releases scheduled against each major.
- Actual releases shipped, both channels: https://www.mozilla.org/en-US/security/advisories/ — dated, one entry per product per release. Count entries, do not estimate.
- Latency: for each release-channel out-of-band advisory, find the first ESR advisory containing the same CVE ids. The advisories list CVEs explicitly, so this is a set difference, not a judgement call.
- The not-backported gap: diff the CVE list of a major's advisory against the same-day ESR advisory. Remember the caveat in §3b.
- ESR support windows: https://endoflife.date/firefox, cross-checked against the last scheduled point release for that ESR series on the calendar.
- Channel of a tree in hand:
cat browser/config/version_display.txt. If it does not end inesr,MOZ_ESRis unset and it is not an ESR tree (build/moz.configure/init.configure:1151).