Follow-up tracker for the install-provenance / self-update work in #2886. Everything below was found while building or reviewing that branch and is not fixed by it. Three items need a decision from you before anyone can write code; the rest are defects with an obvious shape.
Needs a decision
1. Flatpak can never run its own update command
registry.rs returns flatpak install --user --or-update --noninteractive <file> for Channel::Flatpak. A Flatpak build of fresh runs inside org.freedesktop.Platform, which ships no flatpak binary, so that command cannot execute where it is issued. flatpak-spawn appears nowhere in the tree (grep -rn flatpak-spawn crates/ is empty).
Two ways out:
- Prefix with
flatpak-spawn --host — works only if the manifest grants --talk-name=org.freedesktop.Flatpak, which is close to full host access and worth thinking about before granting.
- Concede that Flatpak cannot self-update: route
Channel::Flatpak to Manual with wording pointing at the user's own Flatpak tooling (GNOME Software / flatpak update on the host), never the releases page.
Today the command is generated, shown, and fails at exec time — the worst of the three.
2. snap, scoop, chocolatey are enum variants that target nothing
No packaging job writes those receipts, and no artifact is published for them. #2886 routes all three to Manual, which is honest but leaves dead variants in the public Channel enum. Deleting them is the real fix, and it changes the receipt wire format — a receipt written by a future installer naming a removed variant must still parse. Your call on whether to delete, and if so whether an unknown channel = value should degrade to Unknown rather than fail the parse.
3. FRESH_RELEASES_URL / FRESH_DOWNLOAD_BASE accept any scheme and host
These exist so the update path is reachable outside a real release (that is how #2886 was tested). They are read with no restriction: http:// is accepted, as is any host. The checksum sidecar comes from the same base as the payload, so an attacker who controls the base controls both and the verification proves nothing.
Options: require https://, pin the host to github.com, gate both behind a debug-only build flag, or leave as-is and document them as a testing affordance. Note the same override drives the background check, so a wrong value is silent.
Defects
4. Channel::Zypper is unreachable
Nothing writes a zypper receipt — the openSUSE instructions install the release .rpm directly, which produces a dnf/rpm receipt. The variant and its DownloadPackage route are correct but dead. Either have the openSUSE path record zypper, or fold it into the rpm route.
5. npm update -g cannot cross a 0.x minor
npm update respects the caret range, and ^0.4.6 does not admit 0.5.0. Every npm user is stuck at the current minor until fresh reaches 1.0. npm install -g fresh-editor@latest is the command that actually works.
6. nix profile upgrade silently no-ops on Nix < 2.20
Older Nix requires a match argument (nix profile upgrade '.*' or an index). On 2.19 and earlier the bare form exits 0 having done nothing, so the update reports success and nothing changed.
7. No CI job exercises the update flow
#2886 added a check that provenance resolves from an installed package (config paths asserting channel + Authoritative). Nothing runs an actual update. Every end-to-end verification in that PR was done by hand in containers, so a regression in package_update, the checksum step, or the install command reaches users unchallenged. A single job — install an older .deb, serve the newer one locally, run fresh --cmd update --yes --force, assert dpkg reports the new version — would cover the whole spine.
8. Existing .deb / .rpm installs cannot be reached by the fix
The receipt-lookup fix has to be running to work, so anyone on a package built before it merges stays on "installed manually, can't update itself" until they install a post-fix release by hand. Nothing can be done in code; it needs a release-note line.
9. The popup has never been driven by hand
The plan-derived rows and their exit codes are unit-tested and the underlying check was verified in containers, but nobody has clicked the indicator and looked at the rendered popup. Step 4 of #2886's description is the recipe.
10. Nits
updater.rs — the doc comment on package_update still says "elevating with sudo/doas". The doas fallback was deliberately removed; the comment is stale.
telemetry::stamp_file_path() lands at ~/.local/share/fresh/fresh/telemetry_stamp — DirectoryContext::from_system() already appends fresh and the stamp path appends it again. Pre-existing and harmless, but it makes the file hard to find.
Flaky e2e tests (unrelated, encountered throughout)
Each passed on a re-run of the identical commit with no code change. All are in fresh-editor::e2e_tests; nothing in fresh-update or on the update path has ever flaked.
| Test |
Platform |
Failure |
e2e::code_tour_dock::test_next_key_advances_step |
ubuntu |
TIMEOUT 180.474s |
e2e::code_tour_dock::test_step_range_is_highlighted_in_the_editor |
windows |
TIMEOUT 180.093s |
e2e::plugins::gutter::test_git_gutter_updates_after_save |
macos |
TIMEOUT 180.594s |
e2e::plugins::live_diff::test_live_diff_virtual_line_anchored_to_correct_modified_line |
macos |
FAIL 1.058s |
e2e::plugins::review_diff_ux_bugs::test_issue2117_discard_hunk_with_no_trailing_newline |
windows |
FAIL 2.342s |
Two distinct shapes: 180s timeouts (plugin-startup timing) and sub-3s assertion failures (genuinely non-deterministic, not slow). test_git_gutter_updates_after_save is red on master independently of any branch. test_issue2117... has also flaked on other PRs, making it the most frequent repeat offender.
What #2886 already fixes
For context, so none of the above reads as still-broken:
.deb / .rpm installs can find their receipt at all. debian/rules writes to PREFIX/share/fresh-editor/; receipt::candidate_paths only looked in PREFIX/share/fresh/. Every packaged install resolved to unknown/Unknown and was told it couldn't update itself. Both spellings are now searched — brew, flatpak and nix genuinely use share/fresh/, so neither side could be renamed without stranding installs.
- The update indicator no longer lies. It had two terminal states for three outcomes: it reported failure when nothing had failed, and success when nothing had been installed.
SelfUpdatePhase::ActionRequired, keyed off a new exit code 2, is the third.
.deb/.rpm updates finish in one step. The old path downloaded the package and handed the user a dpkg -i to go and run. It now elevates with sudo inside the interactive update terminal, so the password prompt works where the user already is.
- The confirmation popup is built from the resolved
UpdatePlan — "Update to vX now" / "Show the command" / "Dismiss" — instead of one hardcoded row that meant something different in every channel.
- One mechanism per provenance class; every runtime fallback removed. AUR no longer prefers
yay when it happens to be installed (two identical receipts must not update by different routes) — it uses git clone + makepkg, which works for every AUR user. Privileged installs no longer probe for doas or silently run unprivileged. Asset names are no longer read from the receipt, since a name written at install time describes the version you already have.
- Channels naming commands for packages that were never published are fixed or routed to
Manual: zypper installs the release .rpm, pacman goes back to the AUR, mise uses github:sinelaw/fresh.
- No path dead-ends at the releases page except genuinely unknown/source installs.
- CI asserts resolved provenance from the installed binary rather than
test -f plus grep — the old check proved the file was in the package, which is exactly why it stayed green through the bug above.
- A local root escalation was closed. Packages were staged under a predictable name in world-writable
/tmp and then read by sudo dpkg -i, so anything able to write that path between the checksum check and the install got its bytes installed as root. Staging is now a 0700 directory created atomically with an unpredictable name.
- Non-2xx HTTP responses are errors. The download helper is built with
http_status_as_error(false) and writes nothing on a 404, so a missing asset previously surfaced as "read download: no such file".
Follow-up tracker for the install-provenance / self-update work in #2886. Everything below was found while building or reviewing that branch and is not fixed by it. Three items need a decision from you before anyone can write code; the rest are defects with an obvious shape.
Needs a decision
1. Flatpak can never run its own update command
registry.rsreturnsflatpak install --user --or-update --noninteractive <file>forChannel::Flatpak. A Flatpak build of fresh runs insideorg.freedesktop.Platform, which ships noflatpakbinary, so that command cannot execute where it is issued.flatpak-spawnappears nowhere in the tree (grep -rn flatpak-spawn crates/is empty).Two ways out:
flatpak-spawn --host— works only if the manifest grants--talk-name=org.freedesktop.Flatpak, which is close to full host access and worth thinking about before granting.Channel::FlatpaktoManualwith wording pointing at the user's own Flatpak tooling (GNOME Software /flatpak updateon the host), never the releases page.Today the command is generated, shown, and fails at exec time — the worst of the three.
2.
snap,scoop,chocolateyare enum variants that target nothingNo packaging job writes those receipts, and no artifact is published for them. #2886 routes all three to
Manual, which is honest but leaves dead variants in the publicChannelenum. Deleting them is the real fix, and it changes the receipt wire format — a receipt written by a future installer naming a removed variant must still parse. Your call on whether to delete, and if so whether an unknownchannel =value should degrade toUnknownrather than fail the parse.3.
FRESH_RELEASES_URL/FRESH_DOWNLOAD_BASEaccept any scheme and hostThese exist so the update path is reachable outside a real release (that is how #2886 was tested). They are read with no restriction:
http://is accepted, as is any host. The checksum sidecar comes from the same base as the payload, so an attacker who controls the base controls both and the verification proves nothing.Options: require
https://, pin the host togithub.com, gate both behind a debug-only build flag, or leave as-is and document them as a testing affordance. Note the same override drives the background check, so a wrong value is silent.Defects
4.
Channel::Zypperis unreachableNothing writes a zypper receipt — the openSUSE instructions install the release
.rpmdirectly, which produces adnf/rpm receipt. The variant and itsDownloadPackageroute are correct but dead. Either have the openSUSE path recordzypper, or fold it into the rpm route.5.
npm update -gcannot cross a 0.x minornpm updaterespects the caret range, and^0.4.6does not admit0.5.0. Every npm user is stuck at the current minor until fresh reaches 1.0.npm install -g fresh-editor@latestis the command that actually works.6.
nix profile upgradesilently no-ops on Nix < 2.20Older Nix requires a match argument (
nix profile upgrade '.*'or an index). On 2.19 and earlier the bare form exits 0 having done nothing, so the update reports success and nothing changed.7. No CI job exercises the update flow
#2886 added a check that provenance resolves from an installed package (
config pathsasserting channel +Authoritative). Nothing runs an actual update. Every end-to-end verification in that PR was done by hand in containers, so a regression inpackage_update, the checksum step, or the install command reaches users unchallenged. A single job — install an older.deb, serve the newer one locally, runfresh --cmd update --yes --force, assertdpkgreports the new version — would cover the whole spine.8. Existing
.deb/.rpminstalls cannot be reached by the fixThe receipt-lookup fix has to be running to work, so anyone on a package built before it merges stays on "installed manually, can't update itself" until they install a post-fix release by hand. Nothing can be done in code; it needs a release-note line.
9. The popup has never been driven by hand
The plan-derived rows and their exit codes are unit-tested and the underlying check was verified in containers, but nobody has clicked the indicator and looked at the rendered popup. Step 4 of #2886's description is the recipe.
10. Nits
updater.rs— the doc comment onpackage_updatestill says "elevating withsudo/doas". Thedoasfallback was deliberately removed; the comment is stale.telemetry::stamp_file_path()lands at~/.local/share/fresh/fresh/telemetry_stamp—DirectoryContext::from_system()already appendsfreshand the stamp path appends it again. Pre-existing and harmless, but it makes the file hard to find.Flaky e2e tests (unrelated, encountered throughout)
Each passed on a re-run of the identical commit with no code change. All are in
fresh-editor::e2e_tests; nothing infresh-updateor on the update path has ever flaked.e2e::code_tour_dock::test_next_key_advances_stepe2e::code_tour_dock::test_step_range_is_highlighted_in_the_editore2e::plugins::gutter::test_git_gutter_updates_after_savee2e::plugins::live_diff::test_live_diff_virtual_line_anchored_to_correct_modified_linee2e::plugins::review_diff_ux_bugs::test_issue2117_discard_hunk_with_no_trailing_newlineTwo distinct shapes: 180s timeouts (plugin-startup timing) and sub-3s assertion failures (genuinely non-deterministic, not slow).
test_git_gutter_updates_after_saveis red on master independently of any branch.test_issue2117...has also flaked on other PRs, making it the most frequent repeat offender.What #2886 already fixes
For context, so none of the above reads as still-broken:
.deb/.rpminstalls can find their receipt at all.debian/ruleswrites toPREFIX/share/fresh-editor/;receipt::candidate_pathsonly looked inPREFIX/share/fresh/. Every packaged install resolved tounknown/Unknownand was told it couldn't update itself. Both spellings are now searched — brew, flatpak and nix genuinely useshare/fresh/, so neither side could be renamed without stranding installs.SelfUpdatePhase::ActionRequired, keyed off a new exit code 2, is the third..deb/.rpmupdates finish in one step. The old path downloaded the package and handed the user adpkg -ito go and run. It now elevates withsudoinside the interactive update terminal, so the password prompt works where the user already is.UpdatePlan— "Update to vX now" / "Show the command" / "Dismiss" — instead of one hardcoded row that meant something different in every channel.yaywhen it happens to be installed (two identical receipts must not update by different routes) — it usesgit clone+makepkg, which works for every AUR user. Privileged installs no longer probe fordoasor silently run unprivileged. Asset names are no longer read from the receipt, since a name written at install time describes the version you already have.Manual: zypper installs the release.rpm, pacman goes back to the AUR, mise usesgithub:sinelaw/fresh.test -fplusgrep— the old check proved the file was in the package, which is exactly why it stayed green through the bug above./tmpand then read bysudo dpkg -i, so anything able to write that path between the checksum check and the install got its bytes installed as root. Staging is now a 0700 directory created atomically with an unpredictable name.http_status_as_error(false)and writes nothing on a 404, so a missing asset previously surfaced as "read download: no such file".