Repository navigation
Description
Summary
The version_gt() function removes the prerelease suffix from both versions before comparing them:
ver1="${ver1%%-*}"
ver2="${ver2%%-*}"This causes prerelease and stable versions with the same major, minor, and patch numbers to be treated as equal.
For example:
0.3.0-rc.1
0.3.0
Both are normalized internally to:
0.3.0
As a result, arcup does not recognize the stable release as newer than the release candidate.
Repository
circlefin/arc-node
Create issue:
https://github.com/circlefin/arc-node/issues/new
Affected file
arcup/arcup
Lines 261 to 286 in 745ba4e
| version_gt() { | |
| [ "$1" = "$2" ] && return 1 | |
| # Remove 'v' prefix if present | |
| local ver1="${1#v}" | |
| local ver2="${2#v}" | |
| ver1="${ver1%%-*}" | |
| ver2="${ver2%%-*}" | |
| IFS=. read -r major1 minor1 patch1 <<EOF | |
| $ver1 | |
| EOF | |
| IFS=. read -r major2 minor2 patch2 <<EOF | |
| $ver2 | |
| EOF | |
| [ "$major1" -gt "$major2" ] && return 0 | |
| [ "$major1" -lt "$major2" ] && return 1 | |
| [ "$minor1" -gt "$minor2" ] && return 0 | |
| [ "$minor1" -lt "$minor2" ] && return 1 | |
| [ "$patch1" -gt "$patch2" ] && return 0 | |
| [ "$patch1" -lt "$patch2" ] && return 1 | |
| return 1 | |
| } | |
The function is used by the installer update checks:
Lines 413 to 430 in 745ba4e
| local response | |
| local remote_version | |
| response=$(curl_with_headers -fsSL "$ARCUP_BIN_URL" 2>/dev/null || true) | |
| remote_version=$(printf '%s\n' "$response" | awk -F'"' '/^ARCUP_INSTALLER_VERSION=/ {print $2; exit}') | |
| if [[ -z "$remote_version" ]]; then | |
| return | |
| fi | |
| if version_gt "$remote_version" "$ARCUP_INSTALLER_VERSION"; then | |
| echo "" | |
| warn "Your arcup version ($ARCUP_INSTALLER_VERSION) is outdated." | |
| warn "Latest version is $remote_version." | |
| warn "Run 'arcup --self-update' to update to the latest version." | |
| echo "" | |
| fi | |
| } |
Lines 454 to 462 in 745ba4e
| exit 0 | |
| fi | |
| info "Updating from $ARCUP_INSTALLER_VERSION to $remote_version..." | |
| if cp "$_ARCUP_TMP_FILE" "$ARCUP_BIN_PATH" 2>/dev/null; then | |
| chmod 755 "$ARCUP_BIN_PATH" | |
| else | |
| error "Failed to update arcup at '$ARCUP_BIN_PATH' — ensure it is writable" | |
| fi |
Steps to reproduce
Source the script without running the main installation flow:
export ARCUP_SKIP_MAIN=1
source ./arcup/arcupCompare a stable version with a release candidate:
version_gt "0.3.0" "0.3.0-rc.1"
echo $?The function returns a non-zero status, meaning 0.3.0 is not considered greater.
The same issue can be demonstrated in the opposite direction:
version_gt "0.3.0-rc.2" "0.3.0-rc.1"
echo $?Both prereleases are reduced to 0.3.0, so the newer release candidate is also not detected.
Expected behavior
The comparison should follow Semantic Versioning precedence:
0.3.0-rc.1 < 0.3.0-rc.2 < 0.3.0
Therefore:
version_gt "0.3.0" "0.3.0-rc.1"should return success.
Likewise:
version_gt "0.3.0-rc.2" "0.3.0-rc.1"should return success.
Actual behavior
All versions sharing the same major, minor, and patch numbers are treated as equal after their prerelease suffixes are removed.
Examples incorrectly treated as equivalent:
1.0.0-alpha
1.0.0-beta
1.0.0-rc.1
1.0.0
Impact
Users running a prerelease version of arcup may not be notified about or upgraded to the corresponding stable release.
Users may also fail to receive newer prerelease builds when only the prerelease identifier changes.
This can leave users on:
- release-candidate code after a stable release is available;
- older prerelease builds;
- installer versions containing bugs already fixed in a later prerelease or stable version.
Root cause
The implementation compares only numeric core components:
major.minor.patchand intentionally discards everything following -, even though the accepted version format explicitly allows prerelease suffixes:
^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.+-]+)?$The accepted input format and comparison behavior are therefore inconsistent.
Suggested fix
Use a SemVer-aware comparison implementation.
At minimum:
- Compare major, minor, and patch numerically.
- When the core versions are equal:
- a stable version is greater than a prerelease;
- compare dot-separated prerelease identifiers;
- compare numeric identifiers numerically;
- compare non-numeric identifiers lexically;
- numeric identifiers have lower precedence than non-numeric identifiers.
Alternatively, use a small dedicated SemVer utility already available on the target platforms or restrict installer versions to stable releases only.
Example expected comparisons:
1.0.0 > 1.0.0-rc.1
1.0.0-rc.2 > 1.0.0-rc.1
1.0.0-beta.11 > 1.0.0-beta.2
1.0.1 > 1.0.0
2.0.0 > 1.99.99
Tests
Add tests for:
version_gt "1.0.0" "1.0.0-rc.1"
version_gt "1.0.0-rc.2" "1.0.0-rc.1"
version_gt "1.0.0-beta.11" "1.0.0-beta.2"
version_gt "1.0.1" "1.0.0"
version_gt "2.0.0" "1.99.99"Also verify the inverse cases return false:
version_gt "1.0.0-rc.1" "1.0.0"
version_gt "1.0.0-rc.1" "1.0.0-rc.2"
version_gt "1.0.0" "1.0.0"Checklist
- Preserve prerelease identifiers during comparison
- Treat stable releases as newer than matching prereleases
- Compare prerelease identifiers according to SemVer
- Add unit tests for prerelease ordering
- Verify update notifications from RC to stable
- Verify self-update from older to newer prereleases
Activity
osr21 commentedon Jul 18, 2026
The analysis is correct. Here is a tested drop-in replacement for
version_gtthat covers all the cases in the issue.Replacement
version_gtTest results
All cases from the issue, plus additional edge cases, verified locally:
Notes
No external dependencies.
sort -V(GNU coreutils version sort) handles this on Linux but is absent on macOS (BSD sort has no-V). Sincearcuptargets both platforms, pure bash is the right approach.Bash requirement. The replacement uses
[[ ]],(( )),=~, and<<<— all bash-specific. The existing script already requires bash (e.g. line 294 uses[[ ! "$version" =~ ^v[0-9]+... ]]), so this is not a regression from the current POSIX-styleIFS=. read <<EOFhere-document pattern.check_installer_up_to_dateandupdate_arcup(lines 423 and 452) both callversion_gtwithout changes — the replacement is a drop-in.SemVer §11 reference: https://semver.org/#spec-item-11
fix(arcup): honor SemVer prerelease precedence in version_gt
test(arcup): add version_gt SemVer precedence test suite
Order pre-releases of the same version by their identifiers
JspIIV commentedon Sep 15, 2026
Requesting assignment per the new contribution policy. #300 addresses this: keeps the pre-release tags instead of stripping them, orders two pre-releases per SemVer §11.4, strips build metadata first, and bumps
ARCUP_INSTALLER_VERSIONso self-update can deliver it. Tests walk the semver.org §11.4 chain and all fail onmain.