Publish and inspect GitHub releases with gh release
By the end of this guide you will be able to find a repository's releases, inspect one, create a draft or published release from a tag, and move release assets in a controlled way. The examples use GitHub CLI 2.87.3, installed here in March 2026. Allow about 10 minutes if you already have gh authenticated and a repository ready.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
You need GitHub CLI, a Git repository or a known OWNER/REPO, and permission to read or change releases. Check the installed version and authentication first. These are ordinary user commands; no root access is needed.
gh --version
gh auth status
The version check should include gh version 2.87.3 on the system used for this article. If authentication fails, sign in with gh auth login and complete its prompts. Do not paste a token into a shell history or an article.
Checkpoint
You know the repository name and gh auth status reports a usable account.
1. Choose the repository explicitly
When you run a command outside a checked-out repository, pass --repo (or -R) with the repository name. The value can include a GitHub Enterprise host in the form HOST/OWNER/REPO. Inside a local checkout, gh normally infers the repository from Git remotes, but an explicit value prevents a release going to the wrong project.
repo="OWNER/REPO"
gh release list --repo "$repo"
Replace OWNER/REPO with a real value, such as octo-org/widget. The list command returns up to 30 releases by default, newest first, and includes drafts and pre-releases unless you exclude them.
2. List and inspect releases
Use filters when you need a short, script-friendly result. The installed command supports --limit, ascending or descending --order, and JSON output. This example prints tag, release name and publication time without opening a browser.
gh release list --repo "$repo" --limit 10 --exclude-drafts --json tagName,name,isLatest,publishedAt
To inspect the release behind a tag, use view. Without a tag, it shows the latest release. JSON fields include the notes body and asset details, which is useful for checking what a deployment actually published.
gh release view v1.2.3 --repo "$repo" --json tagName,name,isDraft,isPrerelease,isImmutable,assets,body
For a human-readable check, omit --json. For a precise field in a script, add --jq, for example --jq '.tagName'. A failed command normally means the repository, tag, release, permissions or authentication is wrong. Check the exact repository before retrying.
Checkpoint
The tag you intend to use exists in the release list or the missing tag is a deliberate new release.
3. Create a draft or published release
Creating a release changes the remote repository. Review the tag, target commit and notes before running it. If the tag does not exist, gh release create can create it from the default branch. Use --target to select a branch or full commit SHA instead. Use --verify-tag when silently creating a tag would be unsafe.
A draft is a useful review boundary. It can be edited or deleted before publication, and it avoids announcing unfinished notes or assets.
gh release create v1.2.3 --repo "$repo" --target main --title "Widget 1.2.3" --notes-file release-notes.md --draft
Inspect the draft, then publish it only after checking the assets and notes:
gh release view v1.2.3 --repo "$repo" --json isDraft,tagName,assets,body
gh release edit v1.2.3 --repo "$repo" --draft=false
For a release that should exist only when commits have been added since the previous release, include --fail-on-no-commits. For generated notes, use --generate-notes. If the project requires an existing tag, combine --verify-tag with the release command.
Warning
A published release may be protected by repository release immutability. The current CLI documentation says immutable published releases cannot have their tags or assets modified or deleted; draft releases remain editable. Treat publication as a point of no easy return.
4. Download assets without overwriting local files
Download into a temporary or dedicated directory so an existing build artefact is not mistaken for the downloaded file. With a tag, the command downloads matching release assets. Without a tag, provide --pattern or --archive, because the latest release has no unambiguous asset selection.
download_dir="$(mktemp -d)"
gh release download v1.2.3 --repo "$repo" --pattern '*.tar.gz' --dir "$download_dir"
find "$download_dir" -maxdepth 1 -type f -printf '%f\n'
The command does not require elevated privileges. Use --skip-existing to leave files already present in the destination alone. --clobber overwrites them, so do not add it until you have checked the destination and accepted that recovery may require downloading again.
5. Upload and replace assets carefully
Upload takes a tag followed by one or more local files. Check the file path and checksum before sending a build to a public release.
sha256sum dist/widget-linux-amd64.tar.gz
gh release upload v1.2.3 --repo "$repo" dist/widget-linux-amd64.tar.gz
To give an asset a different display label, append # and the label to the filename. Do not confuse the label with a shell comment when quoting the path:
gh release upload v1.2.3 --repo "$repo" 'dist/widget-linux-amd64.tar.gz#Linux amd64 archive'
Warning
--clobber deletes an existing asset with the same name before uploading the replacement. The command documentation warns that a failed upload can leave the original asset gone. Keep a local copy and checksum, and use a new asset name when you need an easy rollback.
Common traps and recovery
- Wrong repository: print the value of
repoand repeat the command with--repoexplicitly. - Wrong release: use the full tag with
gh release view; an omitted tag means latest. - Unexpected tag creation: add
--verify-tag, or set--targetto the reviewed commit. - Download collision: use a new directory or
--skip-existing; reserve--clobberfor a checked destination. - Need to undo a draft: edit it or delete it. Published deletion is destructive, and
--cleanup-tagalso deletes the tag.
Before deleting anything, inspect it one more time:
gh release view v1.2.3 --repo "$repo"
gh release delete v1.2.3 --repo "$repo" --yes
Only add --cleanup-tag when deleting the remote tag is explicitly part of the rollback. A release deletion cannot be made harmless by running it as root, and repository policies may prevent deletion of immutable published releases.
Done means
gh auth statussucceeds for the intended account.- The repository was confirmed with
--repoor a checked-out remote. gh release viewconfirmed the exact tag, notes, draft state and assets.- Downloads went to a known directory without an accidental overwrite.
- Any create, publish, upload or delete action was reviewed before execution.