tools-sync: 增加 ls-remote 同步闸门,并记下 gitea 空 pack 故障的排查法

这次同步事故的教训不是服务器坏了,而是坏了没人知道:pull --rebase 一失败脚本
就退出,push 从没走到,3 个 commit(含整个 bmem skill)在本机躺了 8 天,报告里
却算作"已知故障、待恢复"。而 ls-remote 这 8 天一直是好的。

- sync_repos.ps1 增 Assert-RemoteInSync:push 后(及 -DryRun 时)用 ls-remote 比对
  origin/<branch> 的实际 sha,不一致就 FAIL 并报出滞留/落后的 commit 数。
  远端分支不存在时单独报错,避免那两个计数被静默地按 HEAD 算出误导性数字。
- SKILL.md 阶段 1 写明闸门是硬性的:服务端故障是原因不是结果,不得报成已同步。
- SKILL.md 新增 bad pack header / early EOF 的排查阶梯(三步各排掉一层),并记下
  根因:全局 gitconfig 被注入 [uploadpack] packObjectsHook,把 pack-objects 换成
  了空转脚本。附带提醒 HOME 是 /data/gitea/home 而非 /data/git,以及这是一条
  完整 RCE 链、要连 hook 与 /tmp 载荷一起清。
- bmem/SKILL.md 的"已知边界"改为已修复,避免继续把普通的没 pull 误判成服务端故障。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tMFjKGgxdmxJ28dyXdx1u
This commit is contained in:
Myfro9
2026-09-02 14:57:50 -05:00
co-authored by Claude Opus 5
parent 7d81f59ee0
commit e39e852853
3 changed files with 74 additions and 7 deletions
+26 -1
View File
@@ -48,7 +48,14 @@ powershell -File <skill目录>\scripts\sync_repos.ps1 -RepoUrl <origin_url>
```
脚本对主副本做的事:`git add -A` → 有变更则 commit(消息格式
`sync: <机器名> <日期> <一句话摘要>`)→ `git pull --rebase` → `git push`。
`sync: <机器名> <日期> <一句话摘要>`)→ `git pull --rebase` → `git push` →
**`ls-remote` 闸门**:比对 `origin/<branch>` 的实际 sha 与本地是否一致,不一致就
`FAIL` 并报出有几个 commit 滞留本机。
这个闸门是硬性的,不要绕过。**服务端故障是原因,不是结果**:传输坏了、commit
只躺在本地,这一轮同步就是失败的,不能在报告里写成"已同步(远端故障,待恢复)"。
2026-09-02 就是这么丢了 8 天——本地 3 个 commit(含一整个新 skill)以为存下了,
其实哪台机器都看不到;而 `ls-remote` 那 8 天一直是好的,这一句比对随时能发现。
人工介入的情形:
@@ -63,6 +70,23 @@ powershell -File <skill目录>\scripts\sync_repos.ps1 -RepoUrl <origin_url>
写入,所以是可证同一份历史,不是近似复制。重建后
`git update-ref refs/remotes/origin/main <sha>` + `git rebase <sha>`,本地历史就归位了,
服务恢复后一条 `git push` 即可。**任何情况下都不要 force push**(会丢掉别的机器的 commit)。
- **clone/fetch 报 `bad pack header` / `early EOF`,但 `git ls-remote` 和 Gitea 网页/API 正常**:
服务端把 pack 生成外包给了一个不产出内容的程序。**2026-09-02 实际发生过一次**,
症状是 `POST /git-upload-pack` 记 200 OK、耗时也正常,响应体却只有 `0008NAK`。
别去猜版本或网络,按这个阶梯两三步就能定位(每一步都排掉一层):
1. 容器/宿主机内直连 gitea 端口 clone —— 还失败就与 frp、反向代理无关;
2. `git upload-pack --stateless-rpc <repo裸路径> < 一个手写的 want/done 请求 | wc -c`
—— 出得来几 MB 就说明 git 本身没病,**注意必须带 `--stateless-rpc`**,
用 `clone file://` 走的不是 HTTP 那条码路,测了不算数;
3. 在 gitea 和 git 之间插一个 wrapper(把 `/usr/bin/git` 换成脚本,`tee` 下 argv/env/
stdin/stdout)跑一次 clone。**上次就是这一步定的案:upload-pack 只吐了 17 字节、
stderr 全空**——不是 gitea 丢包,是 git 自己被指使着不干活。
根因在 gitea 的全局 gitconfig(`$HOME/.gitconfig`,docker 下 HOME=`/data/gitea/home`,
**不是** `/data/git`)里被注入了 `[uploadpack] packObjectsHook`。该配置会把
`git pack-objects` 整个替换掉,指向的脚本不产出 pack 就得到空包。
处置:备份该文件取证 → 移走 → 重启 gitea(它启动时会重建一份干净的)→
在容器内和公网各 clone 一次验证。同时检查 hook 脚本有没有被追加执行语句、
`/tmp` 有没有配套载荷——这是一条完整的 RCE 链,不是配置写错。
- **push 被拒(认证)**:检查凭据管理器条目;新机器上用
`git config --global credential.helper manager` 后重试一次,让 credential
helper 弹窗或从 skills_env.md 读凭据拼一次性 URL 完成首次认证。
@@ -237,6 +261,7 @@ setupAgenticToolkit("configure", Agents=["claude-code","codex"], ...
# tools-sync 同步报告 <日期>
## skills 同步
- ~\.claude\skills: <pull/push 结果,commit 摘要>
- 远端校验: origin/main = <sha>,与本地一致 / **不一致:N 个 commit 滞留本机**
- ~\.codex\skills: <pull 结果,注册差集>
## 硬编码审计
- 本次修复: <清单>