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.
---
title: Get Latest Stable GitHub Tag
subtitle: 'Retrieve the latest stable tag from a GitHub repository using the [GitHub API](https://docs.github.com/en/rest/git/tags). This guide shows you how to fetch tags and filter for stable releases in both Bash and PowerShell.'
author: Jon LaBelle
date: February 2, 2026
source: https://jonlabelle.com/snippets/view/markdown/get-latest-stable-github-tag
gist: https://gist.github.com/jonlabelle/2e091fa88baf70c3b8d6aa36d43c4ee8
---
Retrieve the latest stable tag from a GitHub repository using the [GitHub API](https://docs.github.com/en/rest/git/tags). 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](https://semver.org) format.
## Table of Contents
- [The Challenge](#the-challenge)
- [The Solution](#the-solution)
- [Understanding the API](#understanding-the-api)
- [Bash Implementation](#bash-implementation)
- [Basic Command](#basic-command)
- [macOS Alternative (Without GNU Coreutils)](#macos-alternative-without-gnu-coreutils)
- [Complete Example with Error Handling](#complete-example-with-error-handling)
- [With Authentication](#with-authentication)
- [PowerShell Implementation](#powershell-implementation)
- [Basic Command](#basic-command-1)
- [Complete Example with Error Handling](#complete-example-with-error-handling-1)
- [With Authentication](#with-authentication-1)
- [Summary](#summary)
## The Challenge
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:
1. Filter for stable version tags (using a whitelist approach)
2. Sort them semantically to find the true latest version
3. Handle edge cases like pagination and rate limits
## The Solution
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`).
### Understanding the API
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.
## Bash Implementation
### Basic Command
```bash
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 1
```
**How it works:**
- `curl --silent` fetches the JSON response without progress output
- `sed` extracts tag names from the JSON
- `grep -E` filters for clean semantic versions (whitelist approach)
- `sort -V` performs version-aware sorting
- `tail -n 1` selects 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.
### macOS Alternative (Without GNU Coreutils)
If you don't want to install additional tools, use this version that sorts with `awk`:
```bash
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.
### Complete Example with Error Handling
```bash
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"
```
### With Authentication
For higher rate limits, authenticate with a GitHub token:
```bash
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 Implementation
PowerShell provides native JSON parsing and type-aware sorting, making the implementation more concise.
### Basic Command
```powershell
$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 1
```
**How it works:**
- `Invoke-RestMethod` automatically parses the JSON response
- `Where-Object` filters for clean semantic versions
- `Sort-Object` with `[version]` type casting performs semantic version sorting
- `Select-Object -Last 1` selects the highest version
### Complete Example with Error Handling
```powershell
$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
}
```
### With Authentication
```powershell
$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
}
```
## Summary
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.