fix(ci): gitcode 目标仓前置检查加有界重试(网络抖动不再误判)

2026-10-21 的 main 流水线(dfc4e16)里,「前置检查 gitcode 目标仓是否存在」
首次执行时 gitcode 没有给出预期响应,一次 ls-remote 判失败 ⇒ sync job 红;
重跑立刻全绿。把一次网络抖动翻译成「镜像同步坏了」,是纯噪声。

改法:只对**网络层**失败重试,其余立即失败。
- 网络层(DNS/连接重置/TLS/超时/EOF)→ 最多 3 次,退避 5s、10s
- 仓不存在(gitcode 实测:could not be found + **403**,不是 404)
  → 立即失败,不重试。重试只会把「去建仓」拖成十几秒空等
- 凭据问题(401/403 但不带 not-found 特征)→ 立即失败,不重试
  (token 错了等 15 秒也不会变对)
- 分类顺序上 notfound 必须先于 403:gitcode 的「仓不存在」响应里
  **同时**含 could not be found 与 error: 403,先判 403 会把真问题贴上
  「凭据问题」的错误标签
- 每次尝试打一行「第 N/M 次探测失败(类别):摘要」;重试提示打到 stderr
  (⏳ … 秒后重试),Actions 日志里一眼看得出发生过重试
- 耗尽重试时整段回放每次尝试的摘要,并明确写「判定:网络问题」
  —— 与「仓不存在」/「凭据问题」的判定措辞互不相同,可直接 grep 区分

同口径:**抽成 .github/scripts/check-gitcode-repo.sh,两个 workflow 共用同一个
文件**,而不是两边各写一份「逐字一致」的脚本 —— 后者迟早会漂。ci.yml 的
sync-gitcode-main 与 release.yml 的 sync-gitcode 各留 1 行 bash 调用。
(抽得出公共片段,故没有采用「两边逐字改」的方案。)

token 安全:GC_URL 含 token,永不 echo;所有输出先过 redact()。脱敏两道:
先按字面量遮 token(BRE 元字符先转义),再按 URL 结构遮 userinfo@。
结构式的 password 段**不能排除 `/`** —— 真实 token 可以含 `/`,第一版写成
[^@/[:space:]]* 时 `https://user:tok/en@host` 匹配不上,只剩字面量一道兜住
(判据 C6 首次就是红的)。

判据与变异(9 组正向 / 8 条变异,全部咬住):
判据脚本与变异脚本见 commondev 台账 criteria/b7-gitcode-precheck-retry{,.mutations}.sh。
正向覆盖:重试次数上限(恰好 3 次探测)、仓不存在时 1 次即失败无重试痕迹、
首次成功无重试噪声、抖动后第 2 次成功、凭据问题不重试、token 不进日志、
默认退避真的是 5s/10s(用假 sleep 记录入参,不真等 15 秒)、
两处 workflow 都调同一脚本事 yaml 可解析 + bash -n。
变异 8/8 咬住:默认次数 3→1、退避→0、仓不存在也重试、去掉上限、
去掉重试提示、清空 notfound 特征串、去掉结构式脱敏、ci.yml 退回内联 ls-remote。

过程中修掉三处自己判据的盲区(都不是改产物去迁就判据,而是补判据):
1. M2(退避→0/0)首次未被咬住 ⇒ 补 C7「默认退避 = 5 10」(假 sleep 记录入参)
2. M7(删结构式脱敏)首次未被咬住 ⇒ 补 C7b:userinfo 密码用**别的** secret
   且**含 `/`**。C6 用当前 token 的字面量做输入,字面量替换本身就能抹掉,
   于是结构式规则删了也照样绿;且密码不含 `/` 时兜底的裸 user@ 规则会顶上,
   同样验不出来
3. C7b 第一版断言 grep 'JianFeeeee' 会误报 —— 脚本正常输出里就有
   gitcode.com/JianFeeeee/HologramAgent(那是路径不是凭据),改为断言
   userinfo 形态(://JianFeeeee[:@])

真实网络实测(不是只跑假 git):
- 仓存在:目标仓可访问,rc=0
- 仓不存在:1 次探测即失败,日志含 could not be found + 403,rc=1
- 网络失败:3 次探测 + 2 次退避,总耗时 5.7s(退避调成 1s/1s),rc=1

yaml.safe_load 三个 workflow 全部可解析;bash -n 通过。

未改动(留待裁决):deploy/scripts/upload_assets.py docstring 里
「SDK 仓传 JianFeeeee/homeagent-sdk」指 gitcode 侧、与实测一致
(gitcode: homeagent-sdk=200 / homeagentsdk=404;GitHub 相反),保持原样。
This commit is contained in:
JianFeeeee
2026-10-21 23:46:01 +08:00
parent dfc4e16e65
commit 777f2f810d
3 changed files with 210 additions and 70 deletions

View File

@ -246,48 +246,24 @@ jobs:
# 若不在这里显式探测,缺少目标仓时只会得到 git 的原始报错
# ("repository not found" 混在 push 输出里),看不出是「仓没建」。
# 语义:token 未配置 = 可选项跳过(上面那步);token 有但仓不存在 = 硬失败。
#
# ★ 有界重试(2026-10-21 dfc4e16 的教训):首次跑这一步时 gitcode 网络抖动,
# 一次 ls-remote 判失败 ⇒ job 红;重跑立刻全绿。现在只对**网络层**失败
# 重试(3 次,退避 5s/10s),而「仓不存在」与「凭据问题」**立即失败不重试**
# —— 后两者重试只会把真问题拖长、并把它埋在噪声里。
#
# 实现落在 .github/scripts/check-gitcode-repo.sh,与 release.yml 的
# sync-gitcode **共用同一个文件**(同口径靠同一份代码保证,不靠人抄两遍)。
- name: 前置检查 gitcode 目标仓是否存在
if: steps.tok.outputs.ok == 'true'
env:
GC_TOKEN: ${{ secrets.GITCODE_TOKEN }}
# 显式传环境变量(而不是 shell 局部变量):脚本的默认值与这里
# 相同,但写在 env 里才是**真的传下去**,改仓名时不用改两处逻辑。
GC_REPO: JianFeeeee/HologramAgent
run: |
set -euo pipefail
GC_URL="https://JianFeeeee:${GC_TOKEN}@gitcode.com"
GC_REPO="JianFeeeee/HologramAgent"
# 用 ls-remote 探测:与后面那次 push 走同一个 URL、同一套凭据,
# 比另调一次 API 更贴近真实失败面。
if out=$(GIT_TERMINAL_PROMPT=0 git ls-remote --heads \
"${GC_URL}/${GC_REPO}.git" 2>&1); then
echo " 目标仓可访问:gitcode.com/${GC_REPO}"
else
# 日志脱敏:git 报错可能把 URL 的 userinfo(token 就在里面)带出来。
# 先按 URL 结构遮 https://<userinfo>@ —— 与 token 内容无关,稳;
# 再尽力遮一次字面量(token 含特殊字符时可能不生效,故只做兜底)。
err=$(printf '%s\n' "$out" | sed -E 's#(https?://)[^@/[:space:]]*@#\1***@#g')
err=$(printf '%s\n' "$err" | sed "s|${GC_TOKEN}|***|g" 2>/dev/null || printf '%s\n' "$err")
case "$err" in
# gitcode 真实报错(2026-10 实测):目标仓不存在时返回
# remote: <CH.00905403> The project you were looking for could not be found.
# fatal: ... The requested URL returned error: 403
# 注意是 "could not be found" 与 403,不是 github 那种 "Repository not found"/404。
*"could not be found"*|*"not found"*|*"Not Found"*|*"404"*|*"does not exist"*|*"does not appear to be a git repository"*)
echo " ✗ gitcode 目标仓不存在:gitcode.com/${GC_REPO}"
echo " 请先在该账号下创建镜像仓(只读镜像),再重跑本 job:"
echo " https://gitcode.com/${GC_REPO}"
;;
*"403"*|*"401"*|*"Forbidden"*|*"Unauthorized"*)
echo " ✗ 无法访问 gitcode 目标仓 gitcode.com/${GC_REPO}:仓不存在,或 token 无权访问"
echo " 若仓已建,请确认 GITCODE_TOKEN 属于该仓的推送账号:"
echo " https://gitcode.com/${GC_REPO}"
;;
*)
echo " ✗ 无法访问 gitcode 目标仓 gitcode.com/${GC_REPO}(网络或凭据问题):"
printf '%s\n' "$err" | sed 's/^/ /'
;;
esac
# 显式失败 —— 不静默跳过,也不 echo 一句就当成功。
exit 1
fi
bash .github/scripts/check-gitcode-repo.sh
- name: 推 main 到 gitcode(快进)
if: steps.tok.outputs.ok == 'true'
env: