Retrieve the latest stable tag from a GitHub repository using the GitHub API. This guide shows you how to fetch tags and filter for stable releases in both Bash and PowerShell.
Note
This strategy assumes the repository tags follow the Semantic Versioning format.
The GitHub API's /repos/OWNER/REPO/tags endpoint returns all tags, including pre-releases (alpha, beta, rc, etc.). Additionally, the API doesn't guarantee tags are returned in any particular order---neither chronological nor semantic. This means we need to:
- Filter for stable version tags (using a whitelist approach)
- Sort them semantically to find the true latest version
- Handle edge cases like pagination and rate limits
We'll use a whitelist regex pattern that matches only clean semantic version tags like 1.2.3 or v1.2.3, automatically excluding anything with extensions (e.g., 1.2.3-beta, v2.0.0-rc1).
The GitHub API returns 30 tags by default. To increase our chances of finding the latest stable release, we'll request 100 tags using ?per_page=100. For repositories with many tags, you may need to implement pagination.
GitHub rate limits unauthenticated requests to 60 per hour. Authenticated requests (using a GitHub token) increase this to 5,000 per hour.
curl --silent "https://api.github.com/repos/OWNER/REPO/tags?per_page=100" |
sed -n 's/.*"name": "\([^"]*\)".*/\1/p' |
grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' |
sort -V |
tail -n 1How it works:
curl --silentfetches the JSON response without progress outputsedextracts tag names from the JSONgrep -Efilters for clean semantic versions (whitelist approach)sort -Vperforms version-aware sortingtail -n 1selects the highest version
Important
On macOS, sort -V may not be available in the default BSD utilities. Install GNU coreutils (brew install coreutils) and use gsort -V instead.
If you don't want to install additional tools, use this version that sorts with awk:
OWNER="GoogleCloudPlatform"
REPO="cloud-sql-proxy"
latest_tag=$(curl --silent --fail "https://api.github.com/repos/$OWNER/$REPO/tags?per_page=100" |
sed -n 's/.*"name": "\([^"]*\)".*/\1/p' |
grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' |
awk -F'[.v]' '{
v = ($1 == "" ? $2 : $1);
print v*1000000 + $(NF-1)*1000 + $NF "\t" $0
}' |
sort -n |
tail -n 1 |
cut -f2)
if [ -z "$latest_tag" ]; then
echo "Error: No stable tag found or API request failed"
exit 1
fi
echo "Latest stable tag: $latest_tag"This approach converts versions to numeric values for sorting (e.g., 1.2.3 becomes 1002003), then extracts the original tag name.
OWNER="GoogleCloudPlatform"
REPO="cloud-sql-proxy"
latest_tag=$(curl --silent --fail "https://api.github.com/repos/$OWNER/$REPO/tags?per_page=100" |
sed -n 's/.*"name": "\([^"]*\)".*/\1/p' |
grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' |
sort -V |
tail -n 1)
if [ -z "$latest_tag" ]; then
echo "Error: No stable tag found or API request failed"
exit 1
fi
echo "Latest stable tag: $latest_tag"For higher rate limits, authenticate with a GitHub token:
OWNER="GoogleCloudPlatform"
REPO="cloud-sql-proxy"
GITHUB_TOKEN="your_token_here"
latest_tag=$(curl --silent --fail \
-H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/tags?per_page=100" |
sed -n 's/.*"name": "\([^"]*\)".*/\1/p' |
grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' |
sort -V |
tail -n 1)
if [ -z "$latest_tag" ]; then
echo "Error: No stable tag found or API request failed"
exit 1
fi
echo "Latest stable tag: $latest_tag"PowerShell provides native JSON parsing and type-aware sorting, making the implementation more concise.
$tags = Invoke-RestMethod -Uri "https://api.github.com/repos/OWNER/REPO/tags?per_page=100"
$tags.name |
Where-Object { $_ -match '^v?\d+\.\d+\.\d+$' } |
Sort-Object { [version]($_ -replace '^v','') } |
Select-Object -Last 1How it works:
Invoke-RestMethodautomatically parses the JSON responseWhere-Objectfilters for clean semantic versionsSort-Objectwith[version]type casting performs semantic version sortingSelect-Object -Last 1selects the highest version
$owner = "GoogleCloudPlatform"
$repo = "cloud-sql-proxy"
try {
$tags = Invoke-RestMethod -Uri "https://api.github.com/repos/$owner/$repo/tags?per_page=100"
$latestTag = $tags.name |
Where-Object { $_ -match '^v?\d+\.\d+\.\d+$' } |
Sort-Object { [version]($_ -replace '^v','') } |
Select-Object -Last 1
if (-not $latestTag) {
Write-Error "No stable tag found"
exit 1
}
Write-Output "Latest stable tag: $latestTag"
}
catch {
Write-Error "Failed to fetch tags: $_"
exit 1
}$owner = "GoogleCloudPlatform"
$repo = "cloud-sql-proxy"
$token = "your_token_here"
$headers = @{
Authorization = "Bearer $token"
}
try {
$tags = Invoke-RestMethod -Uri "https://api.github.com/repos/$owner/$repo/tags?per_page=100" -Headers $headers
$latestTag = $tags.name |
Where-Object { $_ -match '^v?\d+\.\d+\.\d+$' } |
Sort-Object { [version]($_ -replace '^v','') } |
Select-Object -Last 1
if (-not $latestTag) {
Write-Error "No stable tag found"
exit 1
}
Write-Output "Latest stable tag: $latestTag"
}
catch {
Write-Error "Failed to fetch tags: $_"
exit 1
}Both approaches use a whitelist strategy to match only clean semantic version tags, automatically filtering out pre-releases. By sorting the results semantically, we ensure we get the true latest stable version regardless of the API's return order.
- Author: Jon LaBelle
- Date: February 2, 2026
- Source: https://jonlabelle.com/snippets/view/markdown/get-latest-stable-github-tag