An SSH .pub file is only the public half of a key pair. If it is missing, or if it is an old file that no longer matches the private key, regenerate it from the private key:
cp ~/.ssh/id_rsa.pub ~/.ssh/id_rsa.pub.backup 2>/dev/null || true
ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub
chmod 644 ~/.ssh/id_rsa.pub
ssh-keygen -y derives and prints the public key without exposing the private key. This also fixes errors such as:
identity_sign: private key ... contents do not match public
Verify the resulting public-key fingerprint with:
ssh-keygen -lf ~/.ssh/id_rsa.pub
diffwalk's marketed flow is one-shot: your coding agent captures the diff, writes the entire walkthrough, and hands you a rendered review to read. That works for sharing, but when the goal is understanding what the agent just changed, a finished lecture is the wrong shape — you read passively, and the agent's framing never gets challenged.
Two changes made it click for me.
Publish is not part of the loop. diffwalk publish only mints a share link. For your own comprehension, diffwalk view (loopback-only) is already the end of the pipeline — and often you don't need the browser at all:
diffwalk changes # list captured blocks
diffwalk change change-009 # read one block in the terminal
Interleave instead of pre-authoring. Instead of letting the agent write all of explanations.yaml up front, go one block at a time: the agent explains the block and gives its own take (correctness, risks, simpler alternatives), then stops and waits. I answer with mine. We decide jointly whether the block deserves a GitHub comment — the draft is shown in full and posted only after I confirm the exact text. Then, next block.
The agreed explanation for each block still lands in explanations.yaml as the loop runs, so a final diffwalk check doubles as "did we actually cover every change". The review conversation produces the walkthrough document as a side effect, instead of the document replacing the conversation.
One implementation note: I didn't edit diffwalk's own SKILL.md — it ships through npm and an update would clobber the edit. The pair-review loop lives in a separate custom skill beside it.
我有一个手动发推的 CLI(Playwright 驱动登录状态的 Firefox 网页,所以不需要 X API key),还有一个多用户笔记应用(HappyNotes)把新笔记推入 Redis 队列同步到 Mastodon。想让它俩连起来:笔记一发,自动发推。但不想动共享后端、毕竟这是一个很私人的hack,也没法子支持其他用户。
演进三步
1. CLI → 微服务。 把发推核心抽成库,Node 内置 http 起一个服务,只监听 Tailscale IP(100.x.y.z:8090)——内网即鉴权,零依赖、零鉴权代码。任何 tailnet 机器都能入队:
curl -X POST http://100.x.y.z:8090/add \
-H 'content-type: application/json' \
-d '{"text":"hello","post_at":"2026-09-07T09:00:00+12:00"}'
2. 图片走 data URL。 别的机器没有你这台的文件系统,multipart 又要新依赖——所以图片在 JSON body 里以 data:image/png;base64,... 传输,Playwright setInputFiles 喂内存 buffer,最多 4 张。
3. 只读 Redis 旁观者。 这是关键设计:不 LPOP(会偷走 Mastodon 消费者的消息),只每秒 LRANGE queue + ZRANGEBYSCORE processing,用 redis-cli 子进程(还是零新依赖)。然后三道过滤:
userId == OWNER_USER_ID && action == "CREATE" && isPrivate == false
task.id 落盘去重(先写 seen 再发布,失败绝不重试——重试 = 重复推文),冷启动基线首轮只记已见不发布,防止积压刷屏。
代价:一个有意的竞态窗口
队列项最多活 5 秒(消费者 LPOP 后成功后 ZREM,删了就没了,没有 deleted 队列可拣漏)。所以旁观者"几乎不漏但不保证"——如果任务在两次轮询之间被消费完,就错过了。要修复的话要动HappyNotes后端(加一个 synced 队列,成功时 LPUSH + 1 小时 TTL),但只为我一个人需求改全站代码不太值,先接受这个妥协看看。