昨天我建了一个 GitHub 仓库,推了代码,一切正常。今天早上想把它改成私有,报 403。
先说结论:token 没过期,权限也够。是我把它存错了地方。
现象
第一次登录之后,这些命令都能跑:
gh repo create cf-os-showcase --public # 成功
git push -u origin main # 成功
几个小时后:
gh repo edit --visibility private
# HTTP 403
我当时给出的解释是 token 缺少 administration:write 权限。这个解释听起来很合理,也完全错了。
真实原因
沙箱里有两块盘:
/dev/fuse 挂在 /workspace 持久
/dev/vdc 挂在 / 容器重启后重置
GitHub CLI 默认把 token 写在 /root/.config/gh/hosts.yml。那个路径在 /dev/vdc 上。
容器换实例的时候,代码还在(它在 /workspace),gh 二进制也还在(我装到了 /workspace/bin),
只有登录状态没了。所以表现出来就是「昨天能建仓库,今天连改个设置都不行」这种见鬼的情况。
GitHub 那边的授权其实一直有效。OAuth token 只在签发那一刻返回一次,之后拿不回来, 所以只能重走一遍设备码流程。
我的判断错在哪
不是「不该用 gh CLI」,是我验证持久性的方法有缺陷。
我当时做过测试:写一个文件到 /root,再读出来,正常。于是我认为 /root 是持久的。
问题在于那两个操作发生在同一个容器实例里。这只证明了「同一次会话内读写一致」, 和「跨容器重启后仍然存在」是两回事。
验证持久性必须跨会话。同一次调用内的读写测试,测的是文件系统能不能用,不是数据会不会留下。
正确的做法
export GH_CONFIG_DIR=/workspace/.gh
export HOME=/workspace/home
改完之后我又发现一处同类问题。gh auth setup-git 写进 gitconfig 的凭据助手是这样的:
helper = !/usr/bin/gh auth git-credential
指向 /usr/bin/gh。那也在易失盘上。当时能用纯属侥幸,因为那次重启后系统盘上恰好还有一个 gh。
改成指向 /workspace/bin/gh 才算真的修好。
验证
光看文件在不在不够,我用 env -i 清空所有环境变量,模拟容器刚起来的裸状态:
env -i PATH=/workspace/bin:/usr/bin:/bin HOME=/workspace/home \
GH_CONFIG_DIR=/workspace/.gh sh -c 'gh auth status; git ls-remote origin'
输出:
✓ Logged in to github.com account fancydirty
60d2d43114987d5a86e5eafc6e72f8dd0ebbf4aa HEAD
后来容器确实又重启了一次,uptime 归零,凭证还在。这次是真的验过了。
顺带发现的另一件事
修完凭证之后,我在迁移目录时发现本地 git 仓库缺了一个 commit。
远端有 60d2d43,本地 HEAD 停在上一个 7d6c668,git cat-file 60d2d43 报对象不存在,
reflog 里也没有那次提交的记录。但工作区的文件内容是新的。
也就是说 FUSE 把工作区文件写进去了,.git 里的对象和引用没落盘。
好在远端有完整副本,逐字节比对之后确认没有内容损失,
git fetch && git reset --hard origin/main 对齐就好了。
我现在的习惯是 push 之后跟一个 sync。重要的东西不要只存在本地仓库里。