From e776ba30238a4ef04a478eb4004bf47037d1b476 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Mon, 21 Sep 2026 12:23:25 +0200 Subject: [PATCH 1/2] fix(catalog): follow the difftastic and byobu release naming changes difftastic 0.71.0 names its release files difft-0.71.0--...; 0.70.0 used difft--.... The download URL template now includes {version}, so the upgrade stopped failing with "Download failed". byobu tags releases as trustmux-v7.19 as well as 7.19 since the trustmux rename. The trustmux-v tags fill the first page of the tags API, and the installer only accepted plain version tags, so it stopped with "Invalid stable version: ". get_target_tag now accepts both forms, prefers the plain tag for equal versions, and the archive URL uses the tag while the version check uses the number. Assisted-by: claude-code:claude-opus-5 Agent-Session: https://claude.ai/code/session_017qjwSFFBZpMj3uw5bPdrnD Agent-Host: 0493f0 Signed-off-by: Sebastian Mendel --- CHANGELOG.md | 1 + catalog/difftastic.json | 4 ++-- scripts/install_byobu.sh | 24 +++++++++++++++--------- tests/test_catalog_and_collectors.py | 3 ++- tests/test_install_gh_fallback.py | 22 ++++++++++++++++++++-- 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f9004f..478e503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0. - Binary-probe fallback in `guide.sh` when the post-install snapshot refresh is stale. ### Fixed +- difftastic 0.71.0 puts the version into its release file names (`difft-0.71.0-x86_64-unknown-linux-gnu.tar.gz`); the catalog download URL now includes it. byobu is tagged `trustmux-v7.19` since the trustmux rename, and those tags fill the first page of the tags API, so the installer found no stable tag; it now accepts both tag forms. - `cmd_update_local` in MERGE mode now refreshes multi-version cycle entries (`python@3.14`, …) instead of only the base-tool entry. Resolved false-negative "Upgrade did not succeed" messages after successful uv installs. ### Changed diff --git a/catalog/difftastic.json b/catalog/difftastic.json index f83207c..a097006 100644 --- a/catalog/difftastic.json +++ b/catalog/difftastic.json @@ -6,7 +6,7 @@ "homepage": "https://github.com/Wilfred/difftastic", "github_repo": "Wilfred/difftastic", "binary_name": "difft", - "download_url_template": "https://github.com/Wilfred/difftastic/releases/download/{version}/difft-{arch}-unknown-linux-gnu.tar.gz", + "download_url_template": "https://github.com/Wilfred/difftastic/releases/download/{version}/difft-{version}-{arch}-unknown-linux-gnu.tar.gz", "arch_map": { "x86_64": "x86_64", "aarch64": "aarch64" @@ -17,7 +17,7 @@ "priority": 1, "config": { "repo": "Wilfred/difftastic", - "asset_pattern": "difft-x86_64-unknown-linux-gnu.tar.gz" + "asset_pattern": "difft-{version}-x86_64-unknown-linux-gnu.tar.gz" } }, { diff --git a/scripts/install_byobu.sh b/scripts/install_byobu.sh index f62a04e..c15ca5e 100755 --- a/scripts/install_byobu.sh +++ b/scripts/install_byobu.sh @@ -53,18 +53,22 @@ get_installed_version() { fi } -get_target_version() { +# Print the newest stable release tag. Upstream tags releases both as "7.19" +# and, since the trustmux rename, as "trustmux-v7.19"; the prefixed tags fill +# the first page of the tags API, so accept both forms. For equal versions +# the plain tag wins. +get_target_tag() { local tags_json="" - local version="" tags_json="$(github_api_get "repos/$GITHUB_REPO/tags?per_page=100")" || tags_json="" - version="$(printf '%s' "$tags_json" | + printf '%s' "$tags_json" | jq -r 'if type == "array" then .[].name else empty end' | - grep -E '^[0-9]+([.][0-9]+)+$' | - sort -V | - tail -1)" - printf '%s' "$version" + grep -E '^(trustmux-v)?[0-9]+([.][0-9]+)+$' | + awk '{ v = $0; sub(/^trustmux-v/, "", v); print v "\t" ($0 == v ? 1 : 0) "\t" $0 }' | + sort -t "$(printf '\t')" -k1,1V -k2,2n | + tail -1 | + cut -f3 || true } remove_manifest_files() { @@ -84,6 +88,7 @@ remove_manifest_files() { install_byobu() { local version="${1:-}" + local tag="$version" local before="" local after="" local archive="" @@ -96,7 +101,8 @@ install_byobu() { if [ -z "$version" ]; then echo "[$TOOL] Fetching latest stable tag..." >&2 - version="$(get_target_version)" + tag="$(get_target_tag)" + version="${tag#trustmux-v}" fi if ! grep -Eq '^[0-9]+([.][0-9]+)+$' <<<"$version"; then echo "[$TOOL] Error: Invalid stable version: ${version:-}" >&2 @@ -108,7 +114,7 @@ install_byobu() { BUILD_LOG="$BUILD_TMPDIR/build.log" archive="$BUILD_TMPDIR/byobu-$version.tar.gz" stage_dir="$BUILD_TMPDIR/stage" - url="https://github.com/$GITHUB_REPO/archive/refs/tags/${version}.tar.gz" + url="https://github.com/$GITHUB_REPO/archive/refs/tags/${tag}.tar.gz" echo "[$TOOL] Downloading $url..." >&2 if ! curl --proto '=https' --proto-redir '=https' -fL \ diff --git a/tests/test_catalog_and_collectors.py b/tests/test_catalog_and_collectors.py index 4eb2208..87836b0 100644 --- a/tests/test_catalog_and_collectors.py +++ b/tests/test_catalog_and_collectors.py @@ -392,7 +392,8 @@ def test_byobu_installer_is_executable_and_filters_stable_tags(self): content = script_path.read_text() assert "^[0-9]+([.][0-9]+)+$" in content - assert "archive/refs/tags/${version}.tar.gz" in content + # The tag, not the version: releases are tagged "trustmux-v7.19" too + assert "archive/refs/tags/${tag}.tar.gz" in content assert './configure --prefix="$INSTALL_PREFIX"' in content assert "$INSTALL_PREFIX/bin/byobu" in content assert "uninstall)" in content diff --git a/tests/test_install_gh_fallback.py b/tests/test_install_gh_fallback.py index 7518534..2217c96 100644 --- a/tests/test_install_gh_fallback.py +++ b/tests/test_install_gh_fallback.py @@ -92,7 +92,7 @@ def test_fails_cleanly_when_neither_source_answers(self, fake_bin, tmp_path): @skip_on_windows class TestGhErrorBodyFallback: def test_byobu_falls_back_to_curl_when_gh_fails(self, fake_bin, tmp_path): - proc = _run_sourced("install_byobu.sh", "get_target_version", fake_bin, tmp_path) + proc = _run_sourced("install_byobu.sh", "get_target_tag", fake_bin, tmp_path) assert proc.returncode == 0, proc.stderr assert proc.stdout.strip() == "7.18" assert "Cannot index" not in proc.stderr @@ -100,10 +100,28 @@ def test_byobu_falls_back_to_curl_when_gh_fails(self, fake_bin, tmp_path): def test_byobu_uses_gh_result_when_gh_succeeds(self, fake_bin, tmp_path): _write_stub(fake_bin, "gh", f"printf '%s' '{BYOBU_TAGS_JSON}'\n") _write_stub(fake_bin, "curl", "exit 99\n") - proc = _run_sourced("install_byobu.sh", "get_target_version", fake_bin, tmp_path) + proc = _run_sourced("install_byobu.sh", "get_target_tag", fake_bin, tmp_path) assert proc.returncode == 0, proc.stderr assert proc.stdout.strip() == "7.18" + def test_byobu_accepts_trustmux_prefixed_tags(self, fake_bin, tmp_path): + # Real first page of the tags API after the trustmux rename: no plain tag on it + tags = ( + '[{"name":"trustmux-v7.20rc5"},{"name":"trustmux-v7.19"},' + '{"name":"trustmux-v7.19rc17"},{"name":"trustmux-v7.18"}]' + ) + _write_stub(fake_bin, "gh", f"printf '%s' '{tags}'\n") + proc = _run_sourced("install_byobu.sh", "get_target_tag", fake_bin, tmp_path) + assert proc.returncode == 0, proc.stderr + assert proc.stdout.strip() == "trustmux-v7.19" + + def test_byobu_prefers_plain_tag_for_equal_versions(self, fake_bin, tmp_path): + tags = '[{"name":"trustmux-v7.19"},{"name":"7.19"},{"name":"7.18"}]' + _write_stub(fake_bin, "gh", f"printf '%s' '{tags}'\n") + proc = _run_sourced("install_byobu.sh", "get_target_tag", fake_bin, tmp_path) + assert proc.returncode == 0, proc.stderr + assert proc.stdout.strip() == "7.19" + def test_tmux_falls_back_to_release_redirect_when_gh_fails(self, fake_bin, tmp_path): proc = _run_sourced("install_tmux.sh", "get_target_version", fake_bin, tmp_path) assert proc.returncode == 0, proc.stderr From b734c05356e263b8ce146a2ba44c68c33c80e77e Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Mon, 21 Sep 2026 13:00:07 +0200 Subject: [PATCH 2/2] fix(byobu): fall back to the trustmux-v tag for an explicit version Review findings: - install_byobu.sh install 7.20 built the URL from the plain tag only. If a release exists only as trustmux-v7.20, the download failed; and "install trustmux-v7.19" failed the version check. The installer now strips the prefix for the version check, and for a plain version it tries the plain tag first, then trustmux-v. - difftastic asset_pattern is documented as a regex (docs/CATALOG_GUIDE.md); use difft-.*-x86_64-... instead of a {version} placeholder. No code reads the field today. Assisted-by: claude-code:claude-opus-5 Agent-Session: https://claude.ai/code/session_017qjwSFFBZpMj3uw5bPdrnD Agent-Host: 0493f0 Signed-off-by: Sebastian Mendel --- catalog/difftastic.json | 2 +- scripts/install_byobu.sh | 28 ++++++++++++++++++++-------- tests/test_catalog_and_collectors.py | 2 +- tests/test_install_gh_fallback.py | 22 ++++++++++++++++++++++ 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/catalog/difftastic.json b/catalog/difftastic.json index a097006..836fbe2 100644 --- a/catalog/difftastic.json +++ b/catalog/difftastic.json @@ -17,7 +17,7 @@ "priority": 1, "config": { "repo": "Wilfred/difftastic", - "asset_pattern": "difft-{version}-x86_64-unknown-linux-gnu.tar.gz" + "asset_pattern": "difft-.*-x86_64-unknown-linux-gnu.tar.gz" } }, { diff --git a/scripts/install_byobu.sh b/scripts/install_byobu.sh index c15ca5e..32118f7 100755 --- a/scripts/install_byobu.sh +++ b/scripts/install_byobu.sh @@ -89,6 +89,8 @@ remove_manifest_files() { install_byobu() { local version="${1:-}" local tag="$version" + local candidate="" + local -a tags=() local before="" local after="" local archive="" @@ -102,7 +104,12 @@ install_byobu() { if [ -z "$version" ]; then echo "[$TOOL] Fetching latest stable tag..." >&2 tag="$(get_target_tag)" - version="${tag#trustmux-v}" + fi + version="${tag#trustmux-v}" + # An explicit plain version may exist only as trustmux-v + tags=("$tag") + if [ "$tag" = "$version" ]; then + tags+=("trustmux-v$version") fi if ! grep -Eq '^[0-9]+([.][0-9]+)+$' <<<"$version"; then echo "[$TOOL] Error: Invalid stable version: ${version:-}" >&2 @@ -114,13 +121,18 @@ install_byobu() { BUILD_LOG="$BUILD_TMPDIR/build.log" archive="$BUILD_TMPDIR/byobu-$version.tar.gz" stage_dir="$BUILD_TMPDIR/stage" - url="https://github.com/$GITHUB_REPO/archive/refs/tags/${tag}.tar.gz" - - echo "[$TOOL] Downloading $url..." >&2 - if ! curl --proto '=https' --proto-redir '=https' -fL \ - --retry 3 --retry-delay 1 --connect-timeout 10 \ - "$url" -o "$archive"; then - echo "[$TOOL] Error: Failed to download $url" >&2 + url="" + for candidate in "${tags[@]}"; do + echo "[$TOOL] Downloading tag $candidate..." >&2 + if curl --proto '=https' --proto-redir '=https' -fsSL \ + --retry 3 --retry-delay 1 --connect-timeout 10 \ + "https://github.com/$GITHUB_REPO/archive/refs/tags/${candidate}.tar.gz" -o "$archive"; then + url="https://github.com/$GITHUB_REPO/archive/refs/tags/${candidate}.tar.gz" + break + fi + done + if [ -z "$url" ]; then + echo "[$TOOL] Error: Failed to download tag(s): ${tags[*]}" >&2 return 1 fi if ! tar -xzf "$archive" -C "$BUILD_TMPDIR"; then diff --git a/tests/test_catalog_and_collectors.py b/tests/test_catalog_and_collectors.py index 87836b0..39f5ba6 100644 --- a/tests/test_catalog_and_collectors.py +++ b/tests/test_catalog_and_collectors.py @@ -393,7 +393,7 @@ def test_byobu_installer_is_executable_and_filters_stable_tags(self): content = script_path.read_text() assert "^[0-9]+([.][0-9]+)+$" in content # The tag, not the version: releases are tagged "trustmux-v7.19" too - assert "archive/refs/tags/${tag}.tar.gz" in content + assert "archive/refs/tags/${candidate}.tar.gz" in content assert './configure --prefix="$INSTALL_PREFIX"' in content assert "$INSTALL_PREFIX/bin/byobu" in content assert "uninstall)" in content diff --git a/tests/test_install_gh_fallback.py b/tests/test_install_gh_fallback.py index 2217c96..78bd25d 100644 --- a/tests/test_install_gh_fallback.py +++ b/tests/test_install_gh_fallback.py @@ -122,6 +122,28 @@ def test_byobu_prefers_plain_tag_for_equal_versions(self, fake_bin, tmp_path): assert proc.returncode == 0, proc.stderr assert proc.stdout.strip() == "7.19" + def test_byobu_explicit_version_falls_back_to_trustmux_tag(self, fake_bin, tmp_path): + # "install 7.20" when upstream only tagged trustmux-v7.20: the plain tag 404s + for cmd in ("make", "autoreconf", "automake", "autoconf"): + _write_stub(fake_bin, cmd, "exit 0\n") + _write_stub( + fake_bin, + "curl", + """for arg in "$@"; do + case "$arg" in + */refs/tags/trustmux-v7.20.tar.gz) echo "archive" > "${@: -1}"; exit 0 ;; + esac +done +exit 22 +""", + ) + proc = _run_sourced("install_byobu.sh", "install_byobu 7.20", fake_bin, tmp_path) + assert proc.returncode == 1 + # Reached the extract step with the prefixed tag; the stub archive is not a tarball + assert "Invalid source archive: https://github.com/dustinkirkland/byobu/archive/refs/tags/trustmux-v7.20.tar.gz" in ( + proc.stderr + ) + def test_tmux_falls_back_to_release_redirect_when_gh_fails(self, fake_bin, tmp_path): proc = _run_sourced("install_tmux.sh", "get_target_version", fake_bin, tmp_path) assert proc.returncode == 0, proc.stderr