在 GitHub 上发版本,最怕的就是点完 Publish 才发现不对:上传的安装包是旧的、tag 打在了错误的提交上、版本号写错了,甚至包里带了不该带的配置文件。
这种时候第一反应往往是"赶紧删掉重发"。删当然可以,但 GitHub 的 Release 和 Git 的 tag 是两样东西,下游用户、包管理器、缓存也可能已经拿到了旧文件。这篇把我处理这类问题的完整步骤整理一下:什么时候该撤回、怎么删 Release 和 tag、怎么重新打 tag 和重新发布,以及哪些坑需要提前想到。
什么情况下需要撤回 Release
常见的有这几类:
- 构建产物有问题:传错了文件、少了平台、打包脚本出错导致包不能用。
- tag 打错了提交:tag 指向的不是你想发布的那次提交,源码包(Source code zip / tar.gz)也就跟着错了。
- 泄露了敏感信息:安装包或源码里带了
.env、密钥、内部地址。 - 版本号写错:比如本该是 v1.2.0 写成了 v1.20.0,或者 tag 和程序内部的版本号对不上。
不同情况处理方式不一样。只是说明文字写错,编辑一下就行;产物或 tag 错了,才需要考虑删除重发;涉及密钥泄露,删除只是第一步。
先想清楚:编辑、转草稿,还是删除
GitHub 上一个 Release 有几种"撤回"程度,从轻到重:
1. 直接编辑 Release
标题、说明文字写错,或者想补充说明,直接在 Release 页面点编辑修改即可,不影响 tag 和附件。附件传错了也可以在编辑页删掉旧附件、上传新附件。
2. 标记为 Pre-release
如果版本能用但不想让大家当正式版用,可以勾选 "Set as a pre-release"。它会从 "Latest" 的位置撤下来,很多依赖 latest 接口的自动更新逻辑也就不会再拿到它。
3. 转回草稿
想先把它从公开页面撤下来、但保留内容慢慢处理,可以转为草稿。用 GitHub CLI 是这样:
gh release edit v1.2.0 --draft草稿状态的 Release 普通访客看不到,但要注意:tag 仍然存在,源码依然可以通过这个 tag 被拉到。
4. 删除 Release
确定这个版本作废,就直接删除。网页上在 Release 页面点删除按钮即可。
这里有个很多人第一次会踩的坑:删除 Release 并不会删除 tag。Release 只是挂在 tag 上的一层"发布页",删掉之后,仓库的 Tags 列表里那个 v1.2.0 还在,git fetch --tags 照样能拿到。
删除 tag:远端和本地都要删
删除远端 tag
git push origin --delete v1.2.0
# 等价的老写法
git push origin :refs/tags/v1.2.0后一种写法的意思是"把空的引用推到远端的 refs/tags/v1.2.0",也就是删除。当分支和 tag 同名时,写完整的 refs/tags/ 路径可以避免误删分支。
网页上也能删:进入仓库的 Tags 页面,找到对应的 tag 删除。如果仓库配置了 tag 保护规则(Rulesets),可能需要管理员权限才能删。
删除本地 tag
git tag -d v1.2.0只删远端不删本地,下次一不小心 git push --tags,旧 tag 又被推上去了。团队里其他人本地如果也有这个 tag,同样需要各自删除,或者用下面的命令强制同步:
git fetch --tags --prune-tags --force用 gh 一步删除 Release 和 tag
用 GitHub CLI 的话,可以一条命令把 Release 和远端 tag 一起删掉:
gh release delete v1.2.0 --cleanup-tag -y--cleanup-tag 会顺带删除远端 tag,-y 跳过确认。本地 tag 仍然需要自己 git tag -d。
重新打 tag
先确认要发布的那次提交:
git log --oneline -n 10然后打一个附注标签(annotated tag),并推到远端:
git tag -a v1.2.1 -m "Release v1.2.1" <commit-sha>
git push origin v1.2.1我建议发版本都用 git tag -a 打附注标签,而不是轻量标签。附注标签会记录打标签的人、时间和说明,git describe 默认也只认附注标签,排查问题时信息更完整。
推送前可以再确认一下 tag 指向对不对:
git show v1.2.1 --stat重新创建 Release
用 gh release create
gh release create v1.2.1 \
--title "v1.2.1" \
--generate-notes \
--verify-tag \
dist/app-linux-amd64.tar.gz dist/app-darwin-arm64.tar.gz dist/checksums.txt几个参数说明:
--generate-notes:根据两个版本之间合并的 PR 和提交自动生成更新说明,生成后可以再编辑。--verify-tag:如果远端不存在这个 tag 就直接报错,避免 gh 在默认分支最新提交上自动帮你创建一个 tag。- 命令最后跟的文件会作为附件上传。
想先检查一遍再公开,可以加 --draft,确认无误后再到网页上发布,或者用 gh release edit v1.2.1 --draft=false。
只替换附件,不删 Release
如果 tag 和提交都没问题,只是某个安装包传错了,其实没必要删 Release,直接覆盖上传就行:
gh release upload v1.2.0 dist/app-linux-amd64.tar.gz --clobber--clobber 表示同名附件存在时直接覆盖。注意如果你同时提供了校验文件(checksums.txt),也要一起重新生成并覆盖,否则用户校验会失败。
如果 Release 是 GitHub Actions 自动发布的
很多项目的发布流程是"推 tag 触发 Actions 构建并上传",比如:
on:
push:
tags:
- "v*"这种情况下有几点要注意:
- 重新运行(Re-run)用的是原来那次触发时的提交和 ref。如果你把 tag 移到了新的提交上,在旧的运行记录上点 Re-run,构建的还是旧代码。代码有变化时,要通过重新推送 tag 来触发新的运行。
- 删除 tag 再推送同名 tag,会重新触发
push事件。但工作流里用GITHUB_TOKEN推送的 tag 不会触发其他工作流,这是 GitHub 为了防止循环触发的限制。 - 建议给发布工作流加一个
workflow_dispatch手动触发入口,并把 tag 作为输入参数,需要补发或重新构建时就不用折腾删 tag:
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "要发布的 tag,例如 v1.2.1"
required: true工作流里 checkout 时用 ref: ${{ inputs.tag || github.ref }},两种触发方式都能拿到正确的代码。
重要提醒:尽量别复用同一个版本号
技术上,删掉 v1.2.0 再在新提交上打一个 v1.2.0 是可以做到的。但我的建议是:只要版本已经公开过一段时间,就发一个新的补丁版本,比如 v1.2.1,而不是复用旧的 tag。
原因是旧版本可能已经被各种地方拿走并缓存了:
- Go 模块代理:proxy.golang.org 会缓存模块版本,校验和数据库 sum.golang.org 也会记录哈希。同一个版本号换了内容,用户会遇到校验和不匹配的报错。Go 项目处理坏版本的正确方式是在 go.mod 里用
retract声明撤回,然后发新版本。 - npm:一个版本号发布过就不能再用,即使 unpublish 之后也不能重新发布同样的版本号。
- Homebrew 等包管理器:Formula 里记录了下载地址和 sha256,附件内容一变,安装就会因为校验失败而报错。
- 用户本地和 CI 缓存:已经下载过的人手里是旧文件,他们的 lockfile、Docker 镜像里也可能固定了旧的哈希。
另外,如果仓库开启了不可变发布(Immutable releases),已发布 Release 的附件和对应 tag 会被锁定,不能修改或移动,删除后也可能无法复用同一个 tag 名。这本来就是为了防止"同一个版本号内容被偷换"的供应链安全设计,遇到这种情况就老老实实发新版本。
泄露了密钥:删除不等于安全
如果撤回的原因是包里带了密钥、Token 或数据库密码,删除 Release 和 tag 只是收尾工作,最重要的是立即轮换(作废并重新生成)这些凭证。
因为从发布到删除的这段时间里,文件可能已经被下载、被镜像站抓取、被 fork 的仓库保留。只要旧凭证还有效,就要当作已经泄露处理。如果密钥还进了 Git 提交历史,删 tag 也没用,提交本身仍然能被访问到,同样以轮换为先。
和团队、用户说清楚
版本撤回是对外可见的事情,处理完之后建议:
- 在新版本的 Release 说明里写清楚上一个版本为什么撤回、用户需要做什么(比如"v1.2.0 安装包有问题,请升级到 v1.2.1")。
- 在 CHANGELOG 里保留撤回记录,不要假装那个版本没存在过。
- 团队内部同步一下,让大家删掉本地旧 tag,避免被重新推上去。
总结:撤回并重新发布的步骤
- 判断问题类型:只是说明写错就编辑;产物错了考虑覆盖附件;tag 或代码错了才删除重发。
- 涉及密钥泄露,先轮换凭证,再处理 Release。
- 删除 Release:网页删除,或
gh release delete v1.2.0 --cleanup-tag -y。 - 删除 tag:远端
git push origin --delete v1.2.0,本地git tag -d v1.2.0。 - 在正确的提交上打附注标签并推送:
git tag -a v1.2.1 -m "Release v1.2.1",git push origin v1.2.1。 - 重新创建 Release:
gh release create v1.2.1 --generate-notes --verify-tag并上传附件;只需换附件时用gh release upload --clobber。 - 用 Actions 发布的项目,通过重新推送 tag 或
workflow_dispatch触发,不要在旧运行上 Re-run 新代码。 - 已公开的版本优先发新的补丁版本号,不要复用旧 tag。
- 在 Release 说明和 CHANGELOG 里写明撤回原因,通知团队清理本地 tag。
发错版本不丢人,处理得干净、交代得清楚就好。




暂无评论