我花了 $15 百万 token 写了 'fix: typo'

昨天我用 Claude Code 写了一条 commit message。diff 是一行修改:一个注释里的拼写错误。Claude Opus 读取了 diff,思考了两秒钟,生成了 fix: correct typo in auth comment。为此,它用了大约 800 个输入 token 和 30 个输出 token,分别花费 $15 和 $75 每百万 token。总成本:不到一分钱。但是,把这个过程放大到一天 40 次 commits,250 个工作日,一家公司有 200 名开发者使用 coding agents,那么这些看似微不足道的花费会积累成数千美元的开销,付出的只是等同于贴创可贴的智力劳动。 问题并不是 Opus 太贵。问题在于 coding agents 不会区分 $0.001 的任务和 $0.10 的任务。一切都通过同样的大模型执行。生成 commit 信息、分类一个 issue、验证格式 —— 全部使用高成本的模型,就像雇用一位外科医生来贴创可贴。 数据与成本 我们用 Claude Opus 4(上一代,目前大部分生产环境还在用的版本)的价格来算一个账: 任务 输入 Token 数 输出 Token 数 成本 生成 commit 信息(小型 diff) ~800 ~30 $0.014 分类某个 issue ~500 ~50 $0.011 验证 commit 格式 ~300 ~20 $0.006 生成 standup 报告 ~2000 ~200 $0.045 这些任务都不需要一个拥有 2 万亿参数和多步推理能力的大模型。这些仅仅是带有强约束的分类和生成任务。本质上就像是根据颜色对卡片进行分类。 ...

2026年4月5日 · Fernando

我的配置:Claude Code + Ghostty + worktrees 在 Mac 上的极致实践

我现在有三个运行中的 Claude Code 会话。一个在翻译博客文章,另一个在为 CLI 编写测试,还有一个在帮我调试数据流水线。每个会话都运行在独立的 worktree 中,每个会话都开启在 Ghostty 的一个 split 里,而我则通过 Cmd+Alt+方向键 在它们之间快速切换。 我几个月没打开过 iTerm2,几个星期没用过 tmux。现在几乎所有工作都集中在 Ghostty 的一个窗口里。 它是完美的配置吗?当然不是。但它是迄今为止让我最高效的配置?毫无疑问。 为什么选择 Ghostty,而不是别的终端? 一句话总结:Ghostty 和挖矿一样耗电。我详细讲过 GPU终端和电量消耗问题,这一问题并没有改善,这依然是它最大的缺点。如果我用的是电池,我会果断关掉 Ghostty,转而使用 Terminal.app。 但每当电脑插电源时——我80%的时间都会坐在 Studio Display 前的桌子上工作——Ghostty 赢下这场比赛的两个原因与外观美化完全无关: 1. 没有闪屏。 听起来没什么,但当你每天对着终端屏幕盯八个小时时,就完全不一样了。iTerm2 在快速滚动时会有轻微的闪屏;调整窗口大小时会出现显示滞后;切换标签页时会有轻微的画面闪烁。Terminal.app 这些问题更严重。而由于 Ghostty使用 GPU 渲染,它提供的视觉流畅度绝对不会让人眼睛疲劳。当 Claude Code 输出200多行日志时,你滑动查看有哪些问题时,一款没有闪屏的终端和一款有闪屏的终端,体验可以说完全不可同日而语。 2. 支持超大缓存。 我设置了 scrollback-limit = 50000,即每个终端窗口有 50,000 行历史记录。Claude Code 输出日志时非常详细:生成代码、解释代码、运行代码、显示结果,偶尔还会附上一堆自言自语式的冗长分析。对于 iTerm2 或 Terminal.app 默认有限的缓冲区来说,这种频率的日志输出很容易就会丢失早期的上下文。而在 Ghostty 中,我可以随心所欲地向上滚动,甚至找到两小时前的操作记录,非常可靠。 除此之外,它还支持默认分屏(通过按 Cmd+D 快速创建),内建下拉式 Quake 风格终端(`Ctrl+``),但这些只是锦上添花。无闪屏、不丢日志历史才是它的两大核心亮点。 我的工作窗口布局 当我需要同时处理多个独立任务时,我的 Ghostty 窗口通常如下: ┌──────────────────────────────────┬──────────────────────────────────┐ │ │ │ │ Claude Code (worktree A) │ Claude Code (worktree B) │ │ feature/nueva-validacion │ chore/traducciones │ │ │ │ │ │ │ ├──────────────────────────────────┴──────────────────────────────────┤ │ │ │ 主仓库(main)—— 测试、构建、git log │ │ │ └─────────────────────────────────────────────────────────────────────┘ 上方两个 splits,每个运行一个 worktree 和对应的代理。下方是 main 仓库,用于运行测试、查看变更和在代理完成任务后进行合并。 ...

2026年3月11日 · Fernando

Codex CLI 没有 worktrees(教你手动实现!)

如果你读过我之前写的 关于 Claude Code 中 worktrees 的文章,你应该知道这个功能的核心就是一个小小的参数:--worktree。执行这个参数之后,每个代理都能在一个隔离的代码仓库副本中运行。每次启动一个代理程序,就在独立的目录中操作。三个代理同时跑也互不干扰,仿佛魔术一般。 现在打开 Codex CLI,想找一下等效的参数,结果发现它根本不存在。 没有 --worktree。没有 --tmux。也没有支持自定义代理的 isolation: worktree 参数。而且,尽管issue #12862 在 GitHub 上已经开了很久,并且已经有人在 fork 中实现了这些功能,但官方始终没有合并代码。到目前为止,Codex CLI 还是无法原生支持多个代理并行操作。 这是否意味着无法使用 Codex 实现并行工作?答案是否定的。这只是意味着你需要自己动手 “折腾” 一下。而这件事说穿了,不过是几个 git 命令和一些预防措施。 计划:手动 worktrees + 每个目录运行一个 Codex 整体思路跟 Claude Code 中的方法类似,只是没有自动化功能。简单来说,就是你自己创建 worktree,自己启动代理程序,事情完了也需要自己清理。这工作听上去有点人工,但它确实管用。 # 从你的主仓库目录开始 cd ~/code/mi-proyecto # 为每个任务创建一个 worktree git worktree add ../mi-proyecto-feat-auth -b feature/auth git worktree add ../mi-proyecto-fix-tests -b fix/tests-rotos git worktree add ../mi-proyecto-refactor -b chore/refactor-db --- 现在你有四个目录:一个主目录和三个 worktree,每个 worktree 都是相互独立的。每个 worktree 拥有自己的分支、暂存区 (*staging area*) 和 HEAD,但共享仓库的元数据(`.git/` 目录),其他一切隔离。 接下来,在每个 worktree 运行一个 Codex: ```bash # 终端 1 cd ../mi-proyecto-feat-auth codex --approval-mode never --sandbox workspace-write \ "实现 JWT 鉴权中间件,编写测试代码。" # 终端 2 cd ../mi-proyecto-fix-tests codex --approval-mode never --sandbox workspace-write \ "运行测试套件,修复所有失败的测试,一直重复直到全部通过。" # 终端 3 cd ../mi-proyecto-refactor codex --approval-mode never --sandbox workspace-write \ "重构数据库模块以使用连接池功能。" 三个代理,三个任务,它们互不干扰。每个 Codex 只看到自己对应的 worktree,并且只操作相应的分支。 ...

2026年3月11日 · Fernando

五个 Claude Code Worktree 技巧,彻底改变你的工作流

几周前,我写了一篇关于 git worktrees 的文章 —— 讲解了它们是什么,怎么创建,以及为什么它们比多次克隆代码库更好。这些只是基础。 但仅仅掌握这些基础只是成功的一半。而 Claude Code 不仅仅是在 worktree 的基础上运行,它还原生支持 worktree,拥有专门的参数、自动隔离功能,与 tmux 的深度集成。了解 worktree 存在和理解 Claude Code 如何利用它们的巨大差异,就像拥有一辆车并知道它有运动模式一样。 Claude Code 的创始人 Boris Cherny 发布了五个关于充分利用 worktree 的技巧。我把这些技巧全都测试了一遍。有些真的是大大优化了我的工作流,省去了我从今年一月起一直在用的小修小补(ñapa)。下面一起来看看吧。 技巧 1:--worktree —— 一个参数搞定一个 worktree 在经典的 worktree 工作流程中,你需要进行以下这些操作: git worktree add ../mi-proyecto-feature -b feature/algo cd ../mi-proyecto-feature claude 总共三步。虽然不算太复杂,但得想目录名、记住语法,然后还得导航到新目录。如果你一天做五次这个操作,时间一长确实会觉得心累。 Claude Code 将整个过程简化成了一步: claude --worktree 就是这样。Claude 会创建一个临时 worktree,将目录命名为随机生成的名字,自动切换到该目录,并启动会话。当你结束作业后,worktree 会自动清理干净。 我什么时候会用这个功能?每次我想测试一些东西又不想污染当前分支的时候。比如尝试一个激烈的重构,探索另一种设计方案,做某个 spike 实验。如果成功的话,我会合并。如果不成功,worktree 就会消失得无影无踪。 这个功能就像是在你的文本编辑器里打开一个新草稿一样。没有任何负担,甚至无需仪式感。 技巧 2:隔离子代理的 worktrees 如果你已经在 Claude Code 中使用了子代理功能(通过在自定义 agent 中使用 Task,或在主要代理内进行委托操作),你可能会知道这些子代理共享同一个工作目录。这意味着两个子代理可能会覆盖彼此的文件,争抢 git 的 index,或者让 staging area 一团混乱。 ...

2026年3月11日 · Fernando

git-cliff: 自动生成的变更日志(几乎不费力)

107 个提交。从第一天开始就是完美的约定式提交。Feat、fix、refactor、chore — 所有内容都完美标记。那么 CHANGELOG 呢?空的。不存在。一个"明天再写"的文件,已经拖了两个月。 如果这听起来很熟悉,你不是一个人。手动编写变更日志是奥林匹克级别的苦差事。不是说它难 — 而是它乏味、重复,总有更紧急的事情要做。这就是为什么 git-cliff 存在的原因。 什么是 git-cliff(30 秒版本) 这是一个用 Rust 编写的变更日志生成器,它读取你的 git 提交,根据约定式提交解析它们,然后输出按版本和类型分组的 CHANGELOG.md。没有奇怪的依赖,没有插件,没有黑魔法。一个二进制文件,一个配置文件,就完了。 简单来说:你给它你的提交,它返回你拖延了几个月的文件。 brew install git-cliff git cliff --output CHANGELOG.md 这两行字面上就是开始所需的全部。如果你的提交遵循 类型: 描述 约定,git-cliff 无需额外配置就能理解它们。 真实案例:8 个版本的回溯性变更日志 在 Tokamak(我的监控 Claude 配额的菜单栏应用)中,我正好遇到了这个问题:107 个完美提交和一个空白的 CHANGELOG。应用已经是 v1.3.0 版本,但只有一个开始时的 v0.1 标签。 计划很简单: 步骤 1:在每个版本的提交上创建回溯性标签。 git tag v0.2.0 32950f4 # Dashboard, biblioteca, achievements v1 git tag v0.3.0 9c56985 # Rename a Tokamak, 6 idiomas git tag v1.0.0 a283490 # App Store: sandbox, privacy manifest git tag v1.3.0 6248bac # HEAD: multi-provider, fetch pipeline # ... 以此类推 步骤 2:运行 git-cliff。 ...

2026年2月22日 · Fernando

Git工作树:如何让多个AI编程代理并行工作互不干扰

单一checkout的困境 我正在开发一个macOS菜单栏应用,有三个并行需求:消费趋势的走势图、原生通知和桌面小组件。这三个功能独立开发,都计划使用Claude Code实现。 问题在于:Claude Code需要在指定目录工作。而每个Git目录同一时间只能检出(checkout)一个分支。标准的git checkout就如同单车道环岛——每次只能通过一辆车。 如果想让三个功能并行开发,传统方案只有以下几种: 暂存乒乓:反复执行git stash保存更改→切换分支→git stash pop恢复→祈祷不要出现冲突…如此循环直到崩溃或退休,视哪个先到来。 克隆多个仓库:可行,但会产生三个完整的.git/目录副本、三个独立历史记录,以及三倍git fetch操作。资源浪费严重。 顺序开发:老老实实一个接一个开发。稳定可靠,但效率低得像手动执行归并排序。 这些方案都不理想。但Git其实自2015年起就内置了第四种解决方案——只是鲜为人知。 工作树(Worktree):现成的解决方案 Git工作树允许为同一仓库创建多个工作目录,无需克隆完整仓库。没有文件复制,没有魔法技巧。 类比说明:将Git仓库视作图书馆。原先你只有一张阅览桌,每次只能打开一本书。而工作树相当于增添多张阅览桌,每张桌子可打开不同的书,但所有书都来自同一个书库。 ~/code/miapp/ ← 主工作区(main分支) .git/ ← 核心书库(唯一存在) ~/code/miapp-sparkline/ ← 工作树1(feature/sparkline分支) .git ← 符号链接文件(指向主书库) ~/code/miapp-notificaciones/ ← 工作树2(feature/notifications分支) .git ← 另一符号链接 每个目录都是完整的检出副本,包含所有文件。你可以在一个目录编译代码,在另一个目录运行测试,同时让AI编程代理在第三个目录工作——真正并行不悖。 一行命令创建工作树 在项目主目录执行: git worktree add ../miapp-sparkline -b feature/sparkline git worktree add ../miapp-notificaciones -b feature/notifications 就这么简单。两个新目录立即创建完成,各自对应独立分支,共享同一Git数据库。无需克隆操作,无需配置远程仓库,无需复制历史记录。 共享与隔离机制 关键在于理解:所有工作树完全共享Git仓库数据——包括提交历史、分支、标签、远程配置和钩子脚本。当你在sparkline工作树提交代码,notifications工作树可即时看到此次提交,无需执行fetch操作。 但以下内容是独立的: 工作目录文件(每个工作树有自己的文件副本) 暂存区(各自独立的git add状态) HEAD指针(指向不同分支) 简言之:每个工作树的"当前工作状态"是私有的,其他所有仓库数据都是共享的。 与AI编程代理的协作流程 这才是工作树的精髓所在。通过工作树,你可以让多个AI编程代理真正并行工作: # 终端1:在sparkline分支运行Claude Code cd ~/code/miapp-sparkline claude # 终端2:在notifications分支运行Claude Code cd ~/code/miapp-notificaciones claude # 终端3:保持主分支运行 cd ~/code/miapp make run 每个Claude实例都在独立目录、独立分支下操作,拥有独立的.build/缓存目录。彼此互不干扰,无需竞争暂存区,更不需要反复执行stash操作。 ...

2026年2月16日 · Fernando

3900万个密钥在GitHub上泄露,下一个可能就是你的

5分钟。就这么长时间。 一个安全研究员故意在GitHub的公开仓库中发布了一个AWS访问密钥,作为实验。 5分钟后,就有人在用它挖加密货币了。 5分钟。 有机器人24/7扫描GitHub,专门寻找这种东西:暴露的凭证。而且它们很快。比你意识到自己搞砸了要快得多。 数字很可怕 据GitHub统计,2024年有3900万个密钥在公开仓库中泄露。比前一年增加了67%。 专门扫描这类问题的GitGuardian仅在公开仓库中就发现了2370万个新密钥。最糟糕的是:2022年检测到的70%密钥在2024年仍然有效。 两年后。仍然能用。等着有人使用它们。 不只是无名小卒 丰田在GitHub上暴露了AWS凭证,这些凭证可以访问他们的车辆远程信息处理系统。培生集团因为有人在配置文件中留下GitLab令牌而丢失数据。酒店管理公司Otelier因为Bitbucket上暴露的凭证,看着8TB的S3数据被盗。 这不只发生在实习生身上。财富500强企业也会中招。 经典借口:“这只是我的个人项目” 是啊,当然。 问题是那个个人项目使用了和生产环境相同的OpenAI API密钥。或者你的Telegram机器人令牌。或者你的测试数据库凭证,哦惊喜,里面有真实数据因为"这样测试更容易"。 然后有一天你不假思索地执行git push。或者因为想展示给某人看而把仓库从私有改为公开。或者GitHub出现bug临时暴露了私有仓库(这种事发生过)。 然后你发现AWS账单从20欧元变成了2000欧元。一夜之间。 “但我立刻删除了” 又一个经典。 Git是版本控制系统。它的工作就是记住发生的所有事情。删除提交不会从历史记录中删除密钥。强制推送不会从分支中删除它。绝对不会从已经复制它的机器人那里删除。 一旦密钥接触到公开仓库,就算是被泄露了。句号。必须轮换。 灾难金字塔 这是典型项目中密钥管理的演进过程: 级别1:地狱 # config.py AWS_KEY = "AKIAIOSFODNN7EXAMPLE" AWS_SECRET = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 直接写在代码里。已提交。在生产环境中。别笑,这真的存在。 级别2:炼狱 # .env (据说在.gitignore中) AWS_KEY=AKIAIOSFODNN7EXAMPLE AWS_SECRET=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 好一些,但那个.env最终会出现在备份中,在你通过Slack发送的压缩包中,在你卖掉的硬盘中… 级别3:边缘地带 # 系统环境变量中的密钥 export AWS_KEY=... 可以,但你把它们存在哪里?便利贴?桌面上的secrets.txt文件?给自己发的Slack消息? 解决方案:密钥脱离代码,脱离磁盘 在几次差点出事之后,我决定把一切都集中在1Password中,使用其CLI在需要时注入密钥。 概念很简单: 密钥存在1Password中,不在我的磁盘上 代码包含密钥的引用,而不是密钥本身 密钥在运行时注入 .env.template + op inject模式 在每个项目中,我不用包含真实值的.env,而是用一个包含引用的.env.template: # .env.template - 这个文件会提交到GIT # 使用以下命令重新生成: op inject -i .env.template -o .env.local OPENAI_API_KEY=op://FRR DEV/OpenAI/api-key SUPABASE_URL=op://FRR DEV/Supabase Personal/url SUPABASE_SERVICE_KEY=op://FRR DEV/Supabase Personal/service-key DATABASE_URL=postgres://localhost/mydb # 非密钥值直接写入 当我需要真实的.env时,执行: ...

2026年2月5日 · Fernando

为什么git status会这么慢?

性能问题的觉醒 你在数据科学项目上工作了一段时间。手头有二十多个笔记本文件、几张图片,还有三个月前看起来还不错的文件夹结构。 当你执行git status查看修改时…等待。继续等待。在等待的过程中你甚至怀疑电脑是卡死了还是在冥想。 剧透:它没在冥想。它在煎熬。 问题是有名有姓的 Git本身并不慢。慢的是你的仓库。 执行git status时,git需要做两件看似简单实则不然的事: 扫描整个文件树检查变更 逐个general比较文件与暂存区的差异 普通仓库中这个过程是瞬时的。但Jupyter笔记本本质上是伪装成文档的JSON文件——而且不是普通JSON:它包含了代码、输出结果、base64编码的图片、内核元数据,基本上囊括了设计者能想到的所有内容。 一个包含少量图表的"小型"笔记本可能就有几MB。乘以二十个笔记本,你就得到了一个每次查看都要呻吟的仓库。 如果你还重命名了文件夹…git会理解为"删除了50个文件并新增了50个文件"。保证让你’爽’到极致。 方案一:给仓库装个门卫 第一个解决方案优雅得让人懊恼为什么没早点知道西门庆。 FSMonitor的工作原理:不再让git每次扫描整个仓库,而是由操作系统主动通知哪些文件发生了变化。 说白了:就像是门口有个门卫告诉你"只有老张进来了",而不必每次核对全量宾客名单。 启用方法: git config core.fsmonitor true git config core.untrackedcache true 完成。就这么简单。 初次启用后的第一次西git status可能耗时相当(甚至更长,因为要初始化缓存)。但从第二次开始…魔法降临了。 在我的400+文件仓库中,git status从2- períodos3秒变成了真正的瞬时完成。不是"变快了",是真正的心念电转。 兼容性如何? macOS: 支持,使用FSEvents Linux: 支持,使用inotify(只要内核支持) Windows: 支持,使用ReadDirectoryChangesW 所以说,全平台通用。 方案二坏人保存菜谱而非成品照片 FSMonitor加速了扫描过程,但西存在另一个问题:笔记本文件依然庞大。每次执行单元格并保存时,即使代码相同文件内容也会改变,因为输出结果不同。 这意味着: 不可读的diff(谁想看图片的base64?) 膨胀的commit 地狱级merge 解决方案叫nbstripout,人如其名:在提交前剥离笔记本的输出内容。 这好比保存菜谱但不保存成品照片。代码得以保留,结果可以随时重新生成。 使用uv安装: uv add nbstripout uv run nbstripout --install 或用传统pip: pip install nbstripout nbstripout --install --install参数会自动配置git过滤器。此后提交笔记本时都会自动剥离输出。 验证是否生效: git config --get filter.nbstripout.clean 若返回类似nbstripout Ressource则配置成功。 需要保留输出怎么办? 好问题。有时你需要提交已执行的笔记本,比如用于文档或让他人直接查看。 ...

2026年1月19日 · Fernando