Agentic体验:1,324次调用我的CLI,15.9%的错误率

我的CLI最常见的用户并不是我自己 lql 是一个用 Rust 开发的CLI工具,用于管理Linear的问题任务。我开发这个工具是因为现有的替代方案并不能满足我的实际使用需求:让一个自主的AI代理来管理问题任务。 为什么我必须开发自己的CLI Linear的MCP服务是我的第一个尝试。这个想法很优雅:搭建一个MCP服务器,从而直接向代理公开Linear的API。但是在实际使用中,它的运行速度很慢且非常不稳定,每次调用时,代理都需要从头开始构建GraphQL查询。这增加了在每次调用中引发不存在字段的机会。用了一两周后,我把这个方案卸载了。 社区版CLI(schpet 的 linear)是我的第二个尝试。这个工具主要是为人类用户设计的,带有交互式菜单、箭头选择以及确认提示。但问题是,一个代理不能操作交互式菜单。也不适合。 Linear 的“智能代理”功能。 2026年3月,Linear 发布了一个集成了 AI 的智能代理。乍一看听起来很理想,直到你发现其局限性:它仅能在Linear的Web界面内使用,没有终端调用功能,没有API,也无法与外部工具集成。它就是一个嵌在自己用户界面中的聊天机器人。如果你的工作流包括“编程的代理也需要管理问题”,Linear 的代理功能无法满足需求。 基于数据的设计:初步分析 在开发 lql 之前,我解析了Claude Code与Linear交互时165次会话中的每一个错误。结果表明,共记录了500多个错误,370次重试,并估算出每月约有700,000个token被浪费。--sort忘记使用40次;写成了--state "Todo" 而非 --state unstarted共12次;--no-interactive未添加导致CLI挂起,等待键盘输入64次。(全套数据细节见原始文章。) 基于这些数据,我设计了 lql 的用户界面。我并没有猜测一个代理需要什么,而是使用测量得到的数据。这形成了一些基础设计决策:输出格式为精简化版本TOON(每个问题约25个token,而不是繁多的JSON格式),为LLM容易混淆的选项添加别名(例如--status → --state),对值进行标准化(比如Todo 转化为 unstarted,urgent 转化为 1),并提供建议正确命令的错误提示信息,而不是简单地显示“未知选项”。 当时我并不清楚,实际上自己是在应用Postel法则。这是后来才意识到的。 第二次分析:lql在生产环境中的一个月 lql上线已经一个月。Claude Code每天会调用它30到50次,用于创建问题、更新状态、链接依赖、查询细节等。我自己从来不手动使用它,因为对我而言,手动管理问题不是兴趣所在。我有代理来解决这些事情。如果哪天我必须亲自执行lql,那说明出了严重的问题。 lql唯一的用户是一个LLM。这让整个设计变得完全不同。 所以我再次解析了Claude Code调用lql的会话日志,搜寻错误。 指标 数值 分析的会话数 200 lql 的调用次数 1,324 错误数 (is_error: true) 210 错误率 15.9% 15.9%的错误率未包括因为并行调用失败且被Claude取消的调用。这里只计算CLI实际发生的错误。 错误分类 并非所有错误都是一样的。有些表明缺乏约定,而有些则表明需要添加操作。 错误 频率 实例 未找到Label 20 --label tokamak (在该团队中不存在) --title 用作 create 的选项 8 lql create --title "Epic: ..." --team PROD 将 show/get 写成 view 6 lql show PROD-911 relate 参数顺序错误 12 lql relate PROD-834 PROD-833 blocked-by 尝试 update --team (移动问题任务) 超过15次 lql update PRIV-32 --team PROD relates 写成 related 2 lql relate PROD-912 relates PROD-910 --body 用于 comment 1 lql comment PROD-926 --body "文本" --comments 用于 view 1 lql view PROD-824 --comments 其余是Linear的API错误、1Password认证问题、或shell错误(长heredocs中的引号破裂)。 ...

2026年4月29日 · Fernando

在Codex中,技能不是/命令(在Claude Code中几乎是)

TL;DR:如果你在使用Codex,command(命令) 用于控制会话或应用程序,而skill(技能) 用于教代理一种工作方法。在Claude Code中,目前的文档已经将技能视为可以用/skill-name直接调用的东西,所以这两种概念在Claude Code中更为融合。但在Codex中恰恰相反:types可以作为技能存在,但/types却可能不存在。 当你从Claude Code切换到Codex时,这种混淆非常常见。而且可以理解。 你创建了一个名为types的技能,回到终端时满怀信心地输入/types……然后Codex看着你,就像你在五金店里要了一杯拉格啤酒。 问题不是技能坏掉了。问题在于,在Codex中,技能和命令并不是一回事。 请注意,这种差异并非表面上的不同,它会彻底改变你设计工作流的方式。 一个让你秒懂的类比 想象一下,Codex就像是一架有两层结构的飞机。 第一层是驾驶舱:按钮、杠杆、指示器。这是命令的栖息地,用于改变会话、客户端或工具的状态,这是操作控制。 第二层是副驾驶手册:流程、标准、检查清单、避免陷阱的指南。这是技能的领域,用于改变代理思考和执行任务的方式。 通俗地讲: 命令是动驾驶舱里的按钮。 技能是改副驾驶脑中的手册。 如果你把手册当成按钮来用,那肯定行不通。 什么是Codex中的命令 在Codex中,命令有两种形式,千万别搞混。 第一种是CLI命令: codex login codex exec "run tests and fix failures" codex resume --last codex apply --- 这很直接明了。这些是应用程序的操作:登录、运行任务、恢复会话、应用变更。如果明天系统中没有了模型,这些命令依然有意义。 第二种是**交互式会话中的斜杠命令**: ```text /model /permissions /personality /agent /status 它们也并非是“华丽的提示语”。它们控制的是实时的会话:更改模型、调整权限、切换人物风格、设定活动线程或改变可见状态。这些就像驾驶舱中的控制按钮。 OpenAI事实上非常清晰地记录了它们的用途:一方面,有专门的斜杠命令页面用于“在交互会话中控制Codex”的说明;另一方面,另有一份独立的技能页面,将技能定义为可重用工作流的创建格式。 这也是为什么有些操作会成为命令而不是技能:因为它们需要可预测性、即时响应和稳定语义。你不希望模型“有创意地解释”/permissions的含义。你只希望它直接更改权限。就这么简单。 什么是Codex中的技能 Codex中的技能是完全不同的东西。它就是一个可重用的工作流,用来教给代理何时运用某种方法、如何思考某个任务并按照特定步骤操作。 这里还有一个细微但重要的区别:OpenAI表示,skill是一种编写格式,而**plugin(插件)**是可安装或分发的单元。换句话说,你会先将工作流设计为技能;如果需要分享或进行封装,就可以把它打包成插件。 明确的例子: $types $improve $owasp $blog 或者,如果更偏语言化: 使用types来审计这个代码仓库 使用improve来检查这个diff 你在这些例子里并不是叫Codex去“切换设置”。你是在告诉它,“当我让你执行这项任务时,请按照这套剧本来操作”。 以我的types技能为例,它不应该是个按钮。它的工作是读取项目代码、检测语言、检查模型、寻找“字符串类型化的代码”、判断一个Optional的使用是否合理并是否能正确建模域状态。这需要背景和判断能力,正是技能擅长的工作。 同理,improve作为一个技能也讲得通:检查diff并不是什么机械性的操作,反而涉及到判断、上下文和优先级的权衡。 为什么在Claude Code中“看起来像是一样的” 这里就是认知的陷阱了。 最新的Claude Code文档对这一点已经毫不避讳了。它谈到技能时,会告诉你可以通过以下方式直接调用: /skill-name 也就是说,在Claude Code中,你认为的某些属于“可重用工作流”的东西是通过斜杠命令语法来调用的。用户体验上,它将Codex中分离的两个概念合并了: ...

2026年3月30日 · Fernando

早上用Claude,下午用Codex:原来我需要的这个双AI助手工作流

TL;DR: 在165次Claude Code会话和27次Codex CLI会话后,我发现了一个清晰的模式:早上使用Claude来处理需要头脑风暴的互动型任务,下午让Codex完成可自动执行的具体任务。这不是哪个更好的问题,而是何时使用哪一个的问题。数据显示,两者结合远胜于单独使用任何一个。 早上九点,手握一杯咖啡,我脑中只有一个模糊的点子,准备重新构建一个模块。我不知道确切该怎么做,只是确信当前的模式并不理想。 于是我启动了Claude Code。 并不是因为它是“最好的”选择——而是因为我需要一种“自言自语”的方式,一个能理解上下文的助手。我告诉Claude:“这个服务的职责太多了,帮我拆分它。”随即,一场对话开始了。它提出一种分离方案,我讨论修改它,我让它探索另一种可能性,它在动手之前还会展示diff。这是一种协作的过程。 三个小时后,模块被成功拆分,测试通过,设计也有所改进。但这是我的Claude配额的40%。而我还列了一堆机械性任务,比如更新集成测试、清理无用的import、重整目录结构。 然后我打开了Codex。 我给它任务,设置为全自动模式,然后悠闲地去吃午饭。 数据揭示的模式 几个月来,我使用一款菜单栏应用监控自己的使用情况,这款工具记录了会话、token用量及耗时。以下是统计数据: Claude Code: 165次会话,160,893条消息,28,052次工具调用。使用模型:Opus 4.5和Opus 4.6。从缓存中读取了56亿token。 Codex CLI: 27次会话。使用模型:GPT-5.4。模式:danger-full-access,审批策略:never。 令我意外的首先是Claude Code的使用时间分布: 09:00 1 ▏ 10:00 5 ██ 11:00 10 ████▌ 12:00 14 ██████▎ 13:00 7 ███ 14:00 16 ███████▏ 15:00 21 █████████▍ 16:00 15 ██████▋ 17:00 18 ████████ 18:00 13 █████▊ 19:00 14 ██████▎ 20:00 9 ████ 21:00 10 ████▌ 22:00 8 ███▌ 我70%的Claude Code会话发生在下午,仅22%在上午。 初看这些数据,我还以为自己“清晨用Claude”的理论行不通。但深入分析每个时间段的任务类型后,一切都渐渐清晰。 上午用于探索,下午用于执行 Claude Code的上午会话少但时长较长。这些会话属于设计阶段:“这部分功能应该如何运作?”、“帮我审阅这个计划”、“能不能提供一些其他选项”。这些是一次又一次的讨论,需要反复修改观点,最终达成决定。 ...

2026年3月26日 · Fernando

删除了150行道歉

TL;DR:我的AI代理有一个246行的指令文件用于管理Linear中的问题。其中150行是变通方法:硬编码的UUID、对curl的回退、“CLI不支持X"的注释。我没有重写它们——而是构建了一个让它们变得不必要的工具。现在那150行变成了零行。 你是否曾经写过一份指令文档,它的长度本身就证明了有什么地方不对劲? 我指的不是合理的文档。我指的是那些开头说"使用工具X”,然后花80%的篇幅解释工具X什么时候不工作以及应该如何替代的文件。那些实际上是为本应构建的工具道歉清单的指令。 我有一个这样的文件。而且很令人尴尬。 150行垃圾的解剖 背景:我与一个AI代理(Claude Code)合作,它管理我在Linear中的问题。为了让代理知道如何操作,我有一个技能文件——一个代理在需要创建、列表或更新问题时会读取的指令文件。 这个文件有246行。其中约100行是合理的文档:存在哪些命令、有哪些团队、使用哪些标签。这是合理的。 其他150行是防御性垃圾。三个类别: 约30行硬编码的UUID。 我使用的CLI不支持--project。所以技能文件在XML表格中包含了17个UUID(5个团队+12个项目)。代理必须找到正确的UUID并手动构建GraphQL突变来分配项目。一个应该是--project Tokamak的操作需要记住一个36字符的UUID。 约25行对curl的回退。 CLI没有搜索功能。没有按项目过滤。创建时没有项目分配。三个基本操作,三个嵌入GraphQL查询的curl块,引号转义,以及认证头。每一个都是等待代理吃掉引号的定时炸弹。 约15行"不支持X"。 五个"CLI不支持"的警告和两个"必需"(每次列表时的–sort和–no-pager)。注意这点:我在工具使用指令中记录工具的缺陷。这就像汽车手册花三页解释雨刷只有在先敲击仪表板后才能工作。 约80行防御性上下文。 整整一节标题为"何时使用API而不是CLI"。目录→UUID映射表。选择标签的启发式方法。当CLI挂起时该怎么办的规则。这些材料存在的唯一原因是工具无能为力。 “小心台阶"的标志 当一个工具有不舒适的界面时,自然的反应是记录变通方法。你写指令。你放警告。你创建一个"常见错误"部分。文档越详细,你就越相信问题已经解决了。 但实际上没有。你放了一个"小心台阶"的标志,而不是修复台阶。 当那些指令的用户是LLM时,问题就成倍增加了。人类读到"不支持–project"会记住(多多少少)。LLM读到它,处理它,三轮对话后还是会使用--project。这不是因为它笨——而是因为它优化完成任务,而--project是分配项目的逻辑路径。禁令在信号的海洋中是噪音。 我在另一篇文章中写过这个问题:对LLM的冗长指令完全等同于放置标志。LLM忽略它们不是因为叛逆。它忽略它们是因为它的功能是找到最直接的路径,而"不要使用–project,而是在这个表格中查找UUID,然后用这个GraphQL查询做curl"不是直接路径——这是一个粗糙的修补。 解决方案不是更好的技能文件 我本可以用更好的指令重写技能文件。更清晰的。有例子的。有图表的。我本可以从246行增加到400行并覆盖每个边缘情况。 这就像扩大标志。 我所做的是构建lql——一个用Rust编写的CLI,专门设计让AI代理(或人类,但主要是代理)可以与Linear交互而不需要生存手册。 设计理念是一句话:错误的路径不应该被禁止,应该是不可能的。 换句话说:你不在文档中禁止--status——你让它工作。你不记录--project在create中不存在——你让它存在。你不维护UUID表格——你自动解析名称。你不提供curl回退——工具没有做不到的事情。你不写"必需:–sort”——你设置合理的默认值。 消失的东西 这是我删除的清单: 防御性垃圾 删除的行数 删除原因 硬编码的UUID(17个ID) ~30 lql自动解析名称 对curl + GraphQL的回退 ~25 lql原生支持search、project、relate “不支持X"的注释(5个) ~15 代理期望的一切都存在 “必需"标志(2个) ~5 合理的默认值,没有必需标志 “何时使用API vs CLI"部分 ~15 没有"vs”——lql什么都能做 上下文→UUID映射表(XML) ~20 从TOML配置自动检测 启发式和防御性规则 ~40 工具是容错的,多余了 总计 ~150 剩下的是合理的文档:存在哪些命令,有哪些团队,使用哪些标签。零变通方法。零道歉。 为什么有效(有趣的部分) 行数的减少很引人注目,但这不是重点。重点是_为什么_那些行是多余的。 旧技能文件中的每行变通方法都存在是因为底层工具是脆弱和不宽容的。脆弱是因为它在合理输入面前失败(--status而不是--state)。不宽容是因为它拒绝而不提供替代(--project不存在,自己想办法)。 当你用容错工具替换脆弱工具时,指令_自动_简化。你不必重写手册——手册自己重写,因为不再有什么需要警告的。 这是解释为什么iPhone手册有10页而打印机手册有200页的同一原理。不是苹果写更好的文档。而是iPhone不需要你解释如何装纸、对齐打印头或清洁滚筒。 容错工具生成简短文档。脆弱工具生成生存手册。 当用户是LLM时,这更加重要。每行指令都是可能被误解、遗忘或矛盾的一行。有150行变通方法的技能文件给它150个错误遵循变通方法的机会。没有变通方法的技能文件给它…零个出错的机会。 ...

2026年3月26日 · Fernando

你的AI写了可以编译但毫无意义的代码(一个linter可以抓住它)

想象一下,你请一个人工来为你制作一个书架。最终交给你的作品看上去很漂亮:架子、螺丝,一切都井井有条。但是你把书架靠在墙上,结果它倒下了。那些螺丝只是道具,看着像螺丝,但其实是塑料做的。 这就是LLM(大语言模型)滥用类型系统时的表现。它交给你的代码可以编译、能通过测试、格式上看起来挑不出毛病。但内部本该是有意义的类型,却被填上了字符串(string)。本该有明确状态的地方,用了nil,而它的意义因读代码的人而异。本该是一个有两个案例的枚举,却被换成了一个== "claude",某天有人可能会写成错拼的== "cladue",但你要等到线上了才会发现。 模型的最爱捷径:万能的字符串 过去几个月,我一直和一个AI助手合作,开发一个中型的Swift应用。它生成的代码干净、结构清晰、命名合理。但有一个模式一再出现:模型尽量避免创建新类型。 这不是因为它懒惰,而是因为创建新类型需要做设计决策,比如:这是一个枚举(enum)吗?有多少个可能值?应该放在哪儿?由谁来导入?而String则无需任何决策。它可以放在任何地方,总是可以编译。 结果就是会生成这样的代码: func sessions(harness: String? = nil) -> [Session] { if let harness { return all.filter { $0.harness == harness } } return all } // 用法: let claudeSessions = sessions(harness: "claude") let codexSessions = sessions(harness: "codex") --- 看起来没问题,确实能运行。但这个代码有三个隐藏的问题: 1. **如果你写错了`"cladue"`,没人会提醒你。**字符串可以是任何内容,但如果是枚举类型,这种错误是可以避免的。 2. **`nil`被用来表示“全部”。** 但这不是定义在某个类型中,而是一种存在于代码注释中、甚至仅仅是你的大脑中的约定。 3. **每个按`harness`过滤的函数都重复了同样的`String? = nil`模式。** 如果你以后想添加一个第三种`harness`,就必须手动搜遍代码中所有的字符串比较。 换句话说,编译器无法帮助你,因为你剥夺了它所有的语义信息。你用一个`String`代替了领域概念。 ## 模型应该写成这样 ```swift enum HarnessID: String, Codable { case claude case codex } enum HarnessFilter { case all case specific(HarnessID) } func sessions(harness: HarnessFilter = .all) -> [Session] { switch harness { case .all: return all case .specific(let id): return all.filter { $0.harnessID == id } } } // 用法: let claudeSessions = sessions(harness: .specific(.claude)) let allSessions = sessions() // 默认是.all — 很明确 现在,"cladue"不会编译。“全部”不再是一个含糊不清的nil,而是一个显式的枚举情况。如果你添加一个第三个harness,编译器会强制你在每个switch语句中处理新情况。它将运行时错误转化为了编译时错误。这才是类型系统本该发挥的作用。 ...

2026年3月23日 · Fernando

与AI助手共事就像和《记忆碎片》的主角生活在一起

想象一下你有一个出色的工作伙伴。他能解决复杂的问题,编写清晰的代码,对你的要求一听就明白。但是,每天下班后,每次你跟他说“打开课程项目”,他都会一脸茫然地看着你,问:“什么课程?在哪里?” 每天如此,毫无例外。 这就像和电影《记忆碎片》(Memento)的主角Leonard Shelby一起生活。他无法形成新的记忆,因此只能把重要的信息纹在身上,避免遗忘。 我每天都会和Claude Code一起处理四到五个项目。几周内,每次我对他说“去课程项目”或者“打开博客”,他都会像维多利亚时代的探险家寻找尼罗河的源头一样,执行find / -name "p101"命令,用五分钟时间在硬盘上搜索我每天都会使用的目录。 这可真让我抓狂。 问题:数字版的顺行性遗忘症 你的AI助手每次启动都像打开了一张白纸。它不知道你住在哪里,不知道你有哪些项目,不知道~/courses/p101/program这个目录存在,也不知道你的博客在~/code/frr.dev,或者你的Ansible代码库叫wuwei并且位于~/code/wuwei/ansible。 每次新会话,都得从头开始。这就像Leonard早晨醒来发现自己躺在那个汽车旅馆的房间里。 最自然的反应就是每次手动告诉它路径:“在/Users/fernando/code/tokamak。”这种方式虽然有效,但就像每天早晨都要向你的朋友重新介绍自己。用不了三个星期,你可能会开始考虑是不是单独工作会更省事。 现实世界的解决方案:zoxide 在给自己“纹身”之前,先说说如何用工具解决人类的困扰。 zoxide 是一种带有记忆功能的cd命令。通俗点说,它是替代cd命令的工具,可以通过输入部分片段记住你曾去过的目录,并智能跳转到最有可能的目标。 # 不再需要这样: cd /Users/fernando/courses/p101/program # 只需这样: z p101 完成了。zoxide知道当你输入"p101"时,你是要跳转到/Users/fernando/courses/p101/program,因为这是你过去47次输入类似路径时访问过的地方。 它使用的是一种频次与最近性相结合的算法。经常访问且刚访问过的目录会被优先排列,而几个月未访问的目录会被降级。这有点像TikTok的推荐算法,只不过是用来管理你的文件系统。 安装步骤简单 brew install zoxide # 添加到你的shell配置文件(我用的是Fish): # 在 ~/.config/fish/config.fish 文件中 zoxide init fish | source 从此,每次执行cd命令都会让zoxide的数据库更新。而z <模式> 让你无需思考就能跳转到目标目录。 z tokamak # → /Users/fernando/code/tokamak z blog # → /Users/fernando/code/frr.dev z wuwei # → /Users/fernando/code/wuwei/ansible z p101 # → /Users/fernando/courses/p101/program 如果遇到模糊匹配(可能有两个以上符合条件的目录),使用zi命令会调出带有交互式选择器的_fzf_工具。 为AI助手配置zoxide:zoxide query 接下来是好消息。zoxide带有一个query命令,它不会改变目录,只是返回最可能的路径: zoxide query p101 # → /Users/fernando/courses/p101/program 对于AI助手来说,这就是黄金功能。与其在整个硬盘上执行find命令,调用zoxide query可以在毫秒级速度返回正确的路径。无需探索,无需猜测。 ...

2026年3月14日 · 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

五个 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

你的AI编程助手不过是个有妄想症的while循环

第一次使用Claude Code重构整个模块时,我几乎产生了宗教般的震撼体验。描述需求后喝了杯咖啡回来,就看见14个文件变更的PR,测试用例全更新,提交信息也像模像样。“这简直是魔法”,我当时这样想。 但根本不是魔法。就是个while循环。 OpenAI的Michael Bolin最近发文拆解了Codex CLI的内部机制。原来这些AI编程助手背后的秘密既不是革命性算法,也不是神秘神经网络,而是一个循环调用LLM、执行工具直到任务完成的简单流程。 让我们来庖丁解牛。 状态机:5阶段循环结构 所有编程助手——Codex、Claude Code、Cursor等都遵循相同的基础模式。Michael Bolin将其描述为5阶段循环: flowchart TD A["1. 提示词组装"] --> B["2. 模型推理"] B --> C{需要工具调用?} C -->|是| D["3. 工具执行"] D --> E["4. 工具结果反馈"] E --> B C -->|否| F["5. 生成最终响应"] F -->|新输入| A 用开发者能懂的话说: 提示词组装:构建包含系统指令、可用工具、读取文件、对话历史等完整上下文的提示词 模型推理:将提示词token化后传给模型,获取思维链/工具调用/文本响应 工具调用:若模型请求工具(读文件/执行命令等),则运行对应操作 工具反馈循环:将工具执行结果作为新增上下文再次传给模型,重复2-4阶段 结果生成:当模型判定任务完成时输出最终响应 就这么简单。没有知识图谱,没有符号规划器,没有复杂架构。本质上就是个封装了LLM的while循环。 优秀助手与平庸助手的差异不在循环结构——它们完全一致——而在于每个阶段的实现细节。 阶段1:提示词工程的艺术 第一阶段是核心所在。在LLM看到任何代码前,助手需要构建包含以下要素的提示词: flowchart LR subgraph 提示词组件["提示词组装"] 方向 TB SP["系统指令\n(角色设定/规则)"] Tools["可用工具\n(读/写/Bash等)"] Ctx["已读取文件/图像"] Inst["CLAUDE.md/AGENTS.md\n(项目规范)"] Env["环境信息\n(OS/git状态等)"] Hist["完整对话历史"] User["用户最新消息"] end SP --> 最终提示 Tools --> 最终提示 Ctx --> 最终提示 Inst --> 最终提示 Env --> 最终提示 Hist --> 最终提示 User --> 最终提示 这里有个关键设计决策:组件顺序至关重要。提示词按稳定性降序排列:系统指令最前(永不变动),工具定义次之(很少变动),最后是动态增长的文件内容和对话历史。 ...

2026年3月11日 · Fernando

从 /simplify 到绝地委员会:如何与 Kent Beck、Martin Fowler 和 Mike Acton 一起进行代码审查

Claude Code 提供了一个名为 /simplify 的 slash command,可以自动审查你的代码。我用它检查了一个大幅变更——大约 8 个文件、500 行代码——结果让人有些意外。它确实发现了些我可能漏掉的点,但也带来了不少无用信息,让我浪费了不少时间。 所以我把它拆解了,然后又重新像拼图一样组装回来。 /simplify 是如何工作的 这是 Claude Code 内置的一项功能(无需额外安装)。它会并行运行三个代理,分别从三个不同的角度来审查代码变更: 代码复用(Code Reuse) —— 是否有可以替换新代码的现有工具? 代码质量(Code Quality) —— 冗余状态、复制粘贴、不良抽象、stringly-typed code 等。 效率(Efficiency) —— 不必要的 I/O、未充分利用的并发、内存泄漏等。 这三个代理会各自给出发现的问题,之后系统尝试直接修复它们。 找到的亮点 代码复用代理发现我在测试代码的两处重复了一个完全相同的辅助函数:相同的名字、相同的代码内容,但分布在两个不同的文件中。我把它提取到一个共享模块里,干净利落。 效率代理指出了一个处理循环中不必要的磁盘操作:每次迭代都加载状态、修改后存储、读取数据、重新加载、再存储。写操作重复了两次,而实际上只需要一次。我没注意到这些隐患,但工具发现了。 还发现了一个内存缓冲区在错误路径中没有清除。如果在分配和释放之间发生错误,会产生内存泄漏。这种问题在主路径上已经被处理到了,但显然 copy-paste 草率遗漏了某些细节。 到这里为止,还算满意。三个发现,全都合法且可操作。但 /simplify 的问题并不在于它发现了什么,而是在于它发现了太多不重要的东西。 存在的缺陷 低级别的问题噪音过多。 它建议我删除一个 struct 的字段,因为 “它与一个计算属性是重复的”。这个字段占用 8 个字节,但被代码和测试中的十多处引用使用。做这个改动带来的代码修改工作量,远远超过了省下这几个字节的好处。 缺乏对项目上下文的理解。 它标记了一个并发模式为 HIGH 严重性,并且的确指出这是一个潜在风险。这一点没错,书面上看是合理的。但实际上,这个问题已经在项目的 CLAUDE.md 文档中记录了,工具链中专门配置了 lint 来应对,并且项目内还有一个相关的 issue 在处理。而 /simplify 并不知道这些,因为它只能基于代码变更的 diff 操作,缺乏项目整体的视角。 无法分辨“错误”和“可优化”。 上文提到的双磁盘操作的确效率低下,但并不是错误。而并发模式的问题就是真正的潜在炸弹。这两者的严重性却都被标记为 MEDIUM,优先级看上去一样,这种扁平的优先级划分很难起到实际帮助。 对外部数据强行推荐 enums。 它建议把某些 DTO 中的字段从字符串转换为枚举,但这些字段只是从外部 API 拉取后用于显示。把它们改成枚举需要自定义解码逻辑,却提供不了任何实际好处——如果对方 API 增加了新值,这样的枚举反倒会导致解析出错。枚举在这种情况下,不如保持字符串更稳妥。 ...

2026年3月9日 · Fernando