Claude Code 第一个小时:读懂一个陌生仓库,修好并提交第一个 bug
装好 Claude Code 之后做什么:本站在一台 Linux 服务器上,用 Express 的源码把三件事实际做了一遍,让它讲清楚项目、找到并修好一个 bug、提交改动。文中是真实的终端输出,以及第一次启动会看到的界面、auto 模式的表现和这一趟花了多少。
本站已实测,测试日期:2026-10-11。
本页目录(8 节)
这篇教程解决一个问题:Claude Code 装好了,接下来的一个小时做什么。
建议是找一个你没读过的仓库,完成三件事:让它把项目讲清楚,让它修一个 bug,让它把改动提交。做完这三件,你就知道它平时是怎么干活的了。
本站把这三件事实际做了一遍。下面的终端输出都是当时的真实输出,只删掉了重复的部分。
| 实测环境 | |
|---|---|
| 日期 | 2026-10-11 |
| 版本 | Claude Code 2.1.296 |
| 系统 | Ubuntu 24.04(一台服务器,通过终端操作) |
| 模型 | Opus 5.5(没有改默认设置) |
| 连接方式 | 通过酷站AI 中转站,不是直连官方 |
| 示例仓库 | Express 的源码,v5.3.0 |
还没装的话,先看Claude Code 安装教程。通过中转站使用的配置方法见Claude Code 接入中转站。
准备:一个仓库,和一个故意埋的 bug
为了让你能照着做,我们用一个大家都认识的项目:Express。lib/ 目录下只有 6 个文件、两千七百多行,测试跑一遍不到十秒。
git clone --depth 1 --branch v5.3.0 https://github.com/expressjs/express.git
cd express
npm install
npm test
这时测试是全过的:
1261 passing (7s)
然后故意改坏一行,并把它提交掉,让工作区保持干净:
git switch -c demo
sed -i "390s/hostname.split('.').reverse()/hostname.split('.')/" lib/request.js
git commit -am "refactor: simplify subdomains getter"
npm test
macOS 自带的 sed 要写成 sed -i ''。改完之后有 4 个测试失败:
1257 passing (7s)
4 failing
1) req
.subdomains
when present
should return an array:
Error: expected [ 'ferrets', 'tobi' ] response body, got [ 'example', 'com' ]
这就是待会儿要让它修的 bug。在你自己的项目里不需要这一步,直接拿一个真实的问题就行。
第一次启动会看到什么
在项目目录里运行:
claude
第一次启动要过三屏,都是按一下键的事:
- 选主题。 默认选中的是 Dark mode,直接回车。以后想改,用
/theme。 - 安全提示。 两条:Claude 会出错,你要对它的动作负责,尤其是运行代码的时候;因为有提示词注入的风险,只在你信任的代码上用它。回车继续。
- 是否信任这个文件夹。 它会写明:Claude Code 将能读取、编辑和执行这里的文件。默认选中的是“No, exit”,要按一下方向键选“Yes, I trust this folder”再回车。
官方文档说第一次使用还会提示登录,订阅账号和 Console 账号是在浏览器里完成认证。我们的环境已经在 ~/.claude/settings.json 里配好了中转站的地址和 key,所以没有出现登录这一步。
之后就是主界面:
▐▛███▛█ Claude Code v2.1.296
▝▜██████▀ Opus 5.5 · API Usage Billing
▝▝ ▝▝ ~/demo/a2-express
▎ Auto mode is now Claude Code's default permission mode.
▎ Auto mode lets Claude handle permission prompts automatically. Claude checks each tool call for risky
▎ actions and prompt injection before executing, runs the ones it assesses as lower-risk, and blocks the rest.
❯
⏵⏵ auto mode on (shift+tab to cycle) · install gh for PR status · ← for agents ◐ medium · /effort
三样东西值得先认一下:
| 位置 | 是什么 |
|---|---|
| 顶部 | 版本号、当前的模型、当前目录 |
| 底部左边 | 当前的权限模式。这里是 auto,按 Shift+Tab 切换 |
| 底部右边 | 思考强度,这里是 medium,用 /effort 改 |
第一件事:让它把项目讲清楚
不需要先告诉它任何背景,也不用手动把文件加进来。官方文档的说法是,Claude Code 会按需要自己去读项目里的文件。直接问:
这个项目是做什么的?用几句话说明,再告诉我入口文件在哪、测试怎么跑。
18 秒后的回答(删掉了末尾一句):
项目简介
这是 Express 框架本身的源码仓库,版本 5.3.0,不是用 Express 写的应用。Express 是 Node.js 上
一个轻量的 Web 框架,提供路由、中间件、请求/响应对象扩展、模板渲染和静态文件服务。很多功能交给了
独立的 npm 包,比如 router、body-parser、serve-static。要求 Node ≥ 18。
入口文件
- index.js:包的入口,只有一行 module.exports = require('./lib/express')。
- lib/express.js:实际的入口,里面创建 app,并导出 express.json、express.static 等方法。
- lib/ 下其他核心文件:
- application.js:app 对象
- request.js / response.js:对 req/res 的扩展
- view.js:模板渲染
- utils.js:工具函数
- examples/ 里是使用示例。
怎么跑测试
测试用 mocha + supertest,node_modules 已经装好了。
npm test # 跑 test/ 和 test/acceptance/ 下的所有测试
npm run test-cov # 带覆盖率(nyc),会生成 HTML 报告
npm run lint # ESLint 检查
只跑单个测试文件:
npx mocha --require test/support/env --check-leaks test/req.subdomains.js
它分清了这是框架本身而不是一个应用,也给出了只跑一个测试文件的命令,后一条平时要自己翻 package.json 才拼得出来。
再问一个要追着代码走的问题:
一个 HTTP 请求进来以后,到我自己写的路由处理函数执行之前,依次经过了 lib/ 里的哪些代码?
按顺序列出来,每一步带上文件名和行号。
这次它跑了四条命令去读代码(cat、grep、sed),37 秒后给出一份七步的清单。开头是这样的:
请求在 lib/ 里只经过两个函数,之后就交给了外部的 router 包(node_modules/router,v2.2.0)。
Express 5 已经把路由匹配和中间件调度整体拆了出去,所以中间最长的一段不在 lib/ 里。
1. lib/express.js:37-39:app 本身就是请求处理函数
app.listen()(lib/application.js:598-599)执行 http.createServer(this),把这个函数注册成
Node 的 request 回调。所以每个请求最先进入的就是这里。
2. lib/application.js:152-178:app.handle
- :169-170:用 Object.setPrototypeOf 把原生 req/res 的原型换成 app.request 和 app.response。
req.query、res.json 这些方法就是这一步挂上的。
- :177:调用 this.router.handle(req, res, done),交给路由器。
……
7. lib/layer.js:142-165:Layer.prototype.handleRequest
const ret = fn(req, res, next) 就是你写的路由处理函数真正被调用的地方。
行号可信吗? 我们抽查了它给的 9 处,包括 lib/express.js:37-39、lib/application.js:152 和 :598-599、lib/request.js:230,以及 router 包里的两处和它的版本号,全部对得上。这不代表它每次都对,重要的地方仍然要自己打开文件看一眼。
问问题的方法,官方文档给了三条建议:
- 先问宽的,再往窄处问。
- 用这个项目自己的术语。
- 可以让它总结项目里的编码约定,或者列一份项目术语表。
第二件事:让它修一个 bug
把症状告诉它,像给同事提 bug 一样:
有用户反馈:访问 tobi.ferrets.example.com 时,req.subdomains 应该返回 ["ferrets", "tobi"],
现在返回的不对。帮我找到原因并修好,修完跑测试确认。
39 秒,它做了四步。下面是屏幕上的记录(有删节):
● Bash(cd /home/ccdemo/demo/a2-express && git show 853f781 && grep -n "subdomains" -A22 lib/request.js | head -40)
⎿ Allowed by auto mode classifier
● Root cause: commit 853f781 removed .reverse(), so slice(2) now drops the first two labels and
returns ["example","com"]. Restoring it.
● Update(lib/request.js)
⎿ Added 1 line, removed 1 line
389 var subdomains = !isIP(hostname)
390 - ? hostname.split('.')
390 + ? hostname.split('.').reverse()
391 : [hostname];
● Bash(npx mocha --require test/support/env --check-leaks test/req.subdomains.js 2>&1 | tail -30; npm test 2>&1 | tail -8)
⎿ Allowed by auto mode classifier
● Bash(git stash -q && npx mocha --require test/support/env --check-leaks test/req.subdomains.js 2>&1 | grep -E "passing|failing"; git stash pop -q && git diff --stat)
⎿ 7 passing (153ms)
4 failing
lib/request.js | 2 +-
最后的总结:
req.subdomains 已经修好,现在访问 tobi.ferrets.example.com 会返回 ["ferrets", "tobi"]。
原因
问题出在最近那次提交 853f781 refactor: simplify subdomains getter,它把 lib/request.js 里
subdomains getter 的 .reverse() 删掉了。……所以这次提交改变了行为,并不是单纯的简化。
修复
在 lib/request.js:390 把 .reverse() 加了回去,只改了这一行。
测试结果
- test/req.subdomains.js:11 个全部通过。改之前跑过一次,有 4 个失败,说明现有测试本来就能发现这个问题。
- npm test 全量:1261 个全部通过。
改动还没有提交。需要的话我可以提交到当前的 demo 分支。
这一段里有三点值得留意。
一、它是从最近的提交里找到原因的。 第一条命令就是 git show 853f781,看的正是我们埋 bug 的那次提交。这不是巧合:官方文档写明,Claude Code 启动时能看到你的 git 状态,包括当前分支、没提交的改动和最近的提交历史。回头看第一个问题,它举的“只跑单个测试文件”的例子正好是 test/req.subdomains.js,多半也是因为它已经看到了那条提交。
所以这个例子对它来说是容易的。真实的 bug 往往没有这么现成的线索。
二、它自己做了验证,而且验证了两遍。 先跑相关的测试文件和全量测试,再用 git stash 把修复临时拿掉,确认不修的时候确实有 4 个失败。第二步没有人要求它做。官方文档把它的工作方式描述成一个循环:收集信息、动手、验证结果,再根据结果决定下一步。
三、整个过程没有问过我们一次。 每条命令下面有一行 Allowed by auto mode classifier。这是 auto 模式:一个分类器在后台审查大多数动作,放行它判断为低风险的,拦下有风险的,而不是每一步都来问你。官方文档说,从 2.1.283 版开始,它是交互式终端会话内置的起始模式。
如果你希望每次改文件、跑命令之前都由你批准,按 Shift+Tab 切到 Manual 模式。还有一个 Plan 模式,只探索、出方案,不改源文件。几种模式怎么选,见权限模式怎么选,改错了怎么退回去。
提 bug 的方法,官方文档的建议是:告诉它复现问题的命令和报错信息,说明问题是每次都出现还是偶尔出现。另外不用指定它读哪个文件、跑哪条命令,把背景和方向说清楚,细节让它自己去找。
改错了怎么办
这次没有用上,但要知道有这条退路:官方文档写明,Claude 在改文件之前会先保存一份快照,按两下 Esc 可以退回到之前的状态。它只管文件的改动,管不了已经对外部系统做出的动作(数据库、接口、部署)。
第三件事:让它提交
提交到 demo 分支,提交说明用英文,一句话。
10 秒:
● Bash(git add lib/request.js && git commit -q -m "fix: restore reverse() in req.subdomains so subdomains are returned in correct order…)
⎿ ef1d439 fix: restore reverse() in req.subdomains so subdomains are returned in correct order
853f781 refactor: simplify subdomains getter
cf6722b 5.3.0 (#7468)
● 修复已经提交到本地的 demo 分支,提交号是 ef1d439……
这次只提交了 lib/request.js,工作区现在是干净的。提交说明末尾按惯例带了一行 Co-Authored-By 署名。
还没有推送到远程。
退出后用 git log 看,提交说明的末尾确实多了一行:
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
这一行是 Claude Code 自己加的。官方文档说明,提交和 PR 的署名来自它加进对话的内置说明;想改掉或去掉,在设置里配 attribution。
它没有推送,并且自己说明了这一点。
这一趟花了多少
在会话里输入 /cost:
Total cost: $0.4185
Total duration (API): 1m 22s
Total duration (wall): 3m 57s
Total code changes: 1 line added, 1 line removed
Usage by model:
claude-opus-5-5: 1.2k input, 4.7k output, 496.4k cache read, 44.2k cache write ($0.4185)
Prompt cache (main): 14 requests · 92% of input tokens from cache · no misses · warm
四个问题,模型实际工作 1 分 22 秒,从启动到做完不到 4 分钟。标题说“第一个小时”,是把你自己读回答、想下一步问什么的时间算进去了。
关于这个数字要知道两点:
- 0.42 美元是 Claude Code 按官方价格估算的,不是中转站实际扣的金额。实际花费看你那家中转站的账单;用订阅账号的话,算的是套餐用量。
- 大头是缓存读取。 输入只有 1200 个 token,缓存读取却有 49.6 万。每一次请求都要带上系统提示词、工具说明和到目前为止的对话,这些内容走缓存,所以便宜,但量很大。为什么缓存这么要紧、怎么不让它白白失效,见Claude Code 怎么省用量。
通过中转站时多出来的一个提示
做到第二个问题时,屏幕上弹出过一个提示,要按回车才继续:
We're changing auto mode to no longer charge for classifier requests in Claude Code.
However, this session isn't eligible because your requests go through api.koozhan.com,
which isn't compatible with this update.
Nothing breaks: auto mode keeps working, and its classifier requests are billed as before.
意思是:auto 模式里那个审查动作的分类器,官方正在改成由服务器顺带完成、不另外收费。但请求经过了网关,服务器的检查到不了这个会话,于是 Claude Code 自己发分类器请求,这些请求照旧计入 token 用量。
官方文档对这个提示有专门的说明:
- 什么都没坏。 按回车继续,auto 模式照常工作。按
Esc是取消这一步动作。 - 最常见的原因就是中间有网关或代理。 订阅账号(Pro、Max、Team)不会看到这个提示。
- 要让它不再出现,需要网关把请求和响应原样透传。如果已经知道网关做不到,可以在启动前设
CLAUDE_CODE_AUTO_MODE_SERVER=0,不再弹这个提示,计费方式不变。官方注明这是个临时的设置,以后可能去掉。
通过别的中转站使用时会不会出现这个提示,要看那家的网关怎么实现,本站只测了这一家。
接下来最常用的几个命令
在终端里:
| 命令 | 做什么 |
|---|---|
claude |
进入交互模式 |
claude "修一下构建报错" |
进入交互模式,并带上第一句话 |
claude -p "解释这个函数" |
问一句就退出,适合接进脚本 |
claude -c |
接着当前目录里最近的那次对话 |
claude -r |
从以前的对话里选一个恢复 |
-c 我们试了:退出后在同一个目录运行 claude -c -p "刚才这个会话里我们改了哪个文件的哪一行?",它答的是 lib/request.js 第 390 行。对话是存在本机的,官方文档说每条消息和工具调用都写在 ~/.claude/projects/ 下的文件里。
在会话里:
| 输入 | 做什么 |
|---|---|
/help |
列出可用的命令 |
/clear |
清空对话,开始一件不相干的事之前用 |
/cost |
看这次会话花了多少 |
/context |
看上下文被什么占用了 |
/model |
换模型 |
/init |
给项目生成一份起步的 CLAUDE.md |
/doctor |
检查安装和配置有没有问题 |
Shift+Tab |
切换权限模式 |
Esc |
立刻打断它。也可以不打断,直接输入更正的话再回车,它做完手上的工具调用就会读到 |
按两下 Esc |
退回到之前的状态 |
/exit |
退出 |
官方文档里还有一句话值得记住:它是对话式的,不需要一次把提示词写完美。第一次不对,不用重来,接着说哪里不对就行。
这次实测没有覆盖到的
- 订阅账号的登录流程。 我们用的是中转站的 key,没有走浏览器登录那一步。
- macOS 和 Windows。 实测在 Linux 上做,其他系统上界面和快捷键可能有差别。
- 没有线索的真实 bug。 这次的 bug 就在最近一次提交里,它一眼就找到了。
- 只跑了一次。 模型每次的回答和走的步骤都会不同,你照着做,看到的输出不会一字不差。
做完这三件事,下一步是给项目写一份 CLAUDE.md,让它每次开工都知道这个项目的规矩,见本系列的下一篇:CLAUDE.md 怎么写。