diff --git a/CHANGELOG.md b/CHANGELOG.md index 464136a..63f94b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [v0.39.1] 2026-06-12 + +### Fixed + +- **Self-update silently failing since v0.38.5**: Release-asset downloads during update checksum verification now send a `User-Agent` header (GitHub rejects requests without one with `403 Forbidden`) and `Accept: application/octet-stream` (without it the asset API URL returns JSON metadata instead of the file), so auto-update and `spotatui update --install` work again. Auto-update failures are now logged instead of silently discarded, and `spotatui update` runs on a blocking thread so it can no longer panic the async runtime. Clients on v0.38.5 through v0.39.0 carry the broken verification in their own binaries and need one manual update to reach this release ([#303](https://github.com/LargeModGames/spotatui/pull/303)). + ## [v0.39.0] 2026-06-12 ### Added diff --git a/Cargo.lock b/Cargo.lock index 40b979a..e6ee0e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5593,7 +5593,7 @@ dependencies = [ [[package]] name = "spotatui" -version = "0.39.0" +version = "0.39.1" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index bc50e59..7f27098 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ documentation = "https://github.com/LargeModGames/spotatui" repository = "https://github.com/LargeModGames/spotatui" keywords = ["spotify", "tui", "cli", "terminal"] categories = ["command-line-utilities"] -version = "0.39.0" +version = "0.39.1" authors = ["LargeModGames "] edition = "2021" license = "MIT" diff --git a/announcements.json b/announcements.json index 0d14a5e..5c18406 100644 --- a/announcements.json +++ b/announcements.json @@ -1,6 +1,15 @@ { "version": 1, "announcements": [ + { + "id": "2026-06-12-self-update-broken", + "title": "Action needed: auto-update is broken in v0.38.5 to v0.39.0", + "body": "A bug in update checksum verification means spotatui v0.38.5 through v0.39.0 cannot update themselves: the update check fails silently on every launch. The fix ships in v0.39.1, but affected versions cannot reach it on their own. Please update once manually (winget upgrade, brew upgrade, your package manager, or the GitHub releases page). Auto-update works again from v0.39.1 onward.", + "level": "warning", + "url": "https://github.com/LargeModGames/spotatui/pull/303", + "starts_at": "2026-06-12T00:00:00Z", + "ends_at": null + }, { "id": "2026-06-12-lua-scripting", "title": "New: Lua plugin scripting", diff --git a/src/cli/update.rs b/src/cli/update.rs index 1ad6a15..222b7b3 100644 --- a/src/cli/update.rs +++ b/src/cli/update.rs @@ -112,12 +112,16 @@ fn verify_release_checksum(release: &self_update::update::Release) -> Result<()> ) })?; + // GitHub's API rejects requests without a User-Agent with 403, and asset URLs + // return JSON metadata instead of the file unless Accept is application/octet-stream. let client = reqwest::blocking::Client::builder() + .user_agent(concat!("spotatui/", env!("CARGO_PKG_VERSION"))) .timeout(std::time::Duration::from_secs(60)) .build()?; let checksum_text = client .get(&checksum_asset.download_url) + .header(reqwest::header::ACCEPT, "application/octet-stream") .send()? .error_for_status()? .text()?; @@ -138,6 +142,7 @@ fn verify_release_checksum(release: &self_update::update::Release) -> Result<()> let binary_bytes = client .get(&asset.download_url) + .header(reqwest::header::ACCEPT, "application/octet-stream") .send()? .error_for_status()? .bytes()?; diff --git a/src/runtime.rs b/src/runtime.rs index 524db6b..3b02bc5 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -482,10 +482,12 @@ fn add_self_update_cli(clap_app: ClapApp) -> ClapApp { } #[cfg(feature = "self-update")] -fn handle_self_update_command(matches: &ArgMatches) -> Result { +async fn handle_self_update_command(matches: &ArgMatches) -> Result { if let Some(update_matches) = matches.subcommand_matches("update") { let do_install = update_matches.get_flag("install"); - cli::check_for_update(do_install)?; + // Must use spawn_blocking because self_update uses reqwest::blocking internally, + // which creates its own tokio runtime and panics if called from an async context. + tokio::task::spawn_blocking(move || cli::check_for_update(do_install)).await??; return Ok(true); } @@ -493,7 +495,7 @@ fn handle_self_update_command(matches: &ArgMatches) -> Result { } #[cfg(not(feature = "self-update"))] -fn handle_self_update_command(_matches: &ArgMatches) -> Result { +async fn handle_self_update_command(_matches: &ArgMatches) -> Result { Ok(false) } @@ -513,10 +515,18 @@ async fn run_auto_update(matches: &ArgMatches, user_config: &UserConfig) { let delay_secs = crate::core::user_config::parse_update_delay_secs(&user_config.behavior.auto_update_delay) .unwrap_or(0); - let update_result = tokio::task::spawn_blocking(move || cli::install_update_silent(delay_secs)) - .await - .ok() - .and_then(|r| r.ok()); + let update_result = + match tokio::task::spawn_blocking(move || cli::install_update_silent(delay_secs)).await { + Ok(Ok(outcome)) => Some(outcome), + Ok(Err(e)) => { + log::warn!("auto-update failed: {:#}", e); + None + } + Err(e) => { + log::warn!("auto-update task panicked: {}", e); + None + } + }; match update_result { Some(cli::UpdateOutcome::Installed(new_version)) => { @@ -633,7 +643,7 @@ screens more often and cost more CPU. Animation-heavy views keep their separate } // Handle self-update command (doesn't need Spotify auth) - if handle_self_update_command(&matches)? { + if handle_self_update_command(&matches).await? { return Ok(()); }