敲完/status,Claude Code 明确回你一行 Base URL:https://api.deepseek.com/anthropic。想把这行换成 TaoToken 通道,先去 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 注册并创建一把 Key,再回到配置文件改一处就行。
问题就在「改哪一处」。这个地址不是 Claude Code 拍脑袋写死的,它来自环境变量、用户级settings.json、项目级 settings,或者某个还在生效的 shell 别名。你只改了其中一处,另一处还在往 DeepSeek 指,/status当然不认账。更烦的是:Base URL 没换对,后面让 Claude Code 生成src/index.js、src/routes/todos.js、跑npm start、用 curl 打接口,全都会跟着抖——不是代码写错,是模型通道根本没接上。这篇就顺着「先定位来源、再改一处、再回/status复核」这条线走,把 Todo REST API 那套实战流程重新跑通一遍。
1. /status 里那行 DeepSeek 地址是从哪冒出来的
Claude Code 自己不会存「上次用的是哪家模型」。它每次启动都重新读一遍环境,所以只要机器上还有任何一个地方写着旧地址,/status就会把那行老配置原样吐出来。你要做的不是反复改,而是把可能的来源列全,一个个排掉。
1.1 四个常见的配置来源
按经验,优先级和隐蔽程度大概是这样的:
- shell 启动文件:
~/.zshrc、~/.bashrc、~/.bash_profile里的export ANTHROPIC_BASE_URL=...。这是最常见的一处,也是你最可能忘了的一处。 - 用户级
~/.claude/settings.json:里面的env段是 Claude Code 启动时注入会话的环境,切模型、换通道一般写在这里。 - 项目级
.claude/settings.json和.claude/settings.local.json:跟着仓库走,换项目就换配置。.local.json通常不进 Git,容易被忽略。 - 包装脚本或别名:有人习惯写
alias claude='ANTHROPIC_BASE_URL=... claude',或者做了个claude.sh。/status看到的地址就来自这种地方。
先做三件事把它们照出来:
echo $ANTHROPIC_BASE_URL env | grep -i anthropic type claude第一条看当前 shell 里有没有残留,第二条把所有相关变量一次列全(ANTHROPIC_AUTH_TOKEN、ANTHROPIC_MODEL一起看),第三条确认你敲的claude到底是可执行文件、别名还是函数。如果type claude输出claude is an alias for ...,那基本就是它在捣乱。
1.2 用 /status 和文件内容对账
把 shell 变量看完,再打开配置文件对一遍:
cat ~/.claude/settings.json cat .claude/settings.json 2>/dev/null cat .claude/settings.local.json 2>/dev/null然后回到 Claude Code 会话里敲/status,把三样东西记下来:当前模型名、Base URL、会话统计。这三项就是你的「基准线」。改配置之前先记一份,改完再对一次,才知道到底哪一项动了。
一个容易忽略的细节:shell 环境变量和settings.json里的env段不是互斥的,两者都可能生效。所以别只改一处就收工,要么统一收敛到settings.json,要么在 shell 里把旧的export注释掉。我的习惯是——shell 里一个ANTHROPIC_*都不留,全部交给settings.json,出问题时只需要看一个文件。
2. 改 ANTHROPIC_BASE_URL:settings.json 与 shell 变量两条路
定位清楚之后,改动本身只有三行。但「三行写在哪」决定了它是一次性的还是长期生效的。下面两条路都给你,选一条就行,别混着用。
2.1 先拿 Key 和模型 ID
打开 TaoToken 注册登录,进控制台创建一把 API Key,复制出来先放好。接下来两个值你会反复用到:
- Base URL:
https://taotoken.net/api,末尾不要加/v1,也不要带任何查询参数。 - 模型 ID:以 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 上的模型广场当时列表为准,别凭记忆写。列表里叫什么,配置里就写什么。
Key 在正文里我一律写成YOUR_API_KEY,你自己替换成真实值。不要把真实 Key 提交进 Git,包括.claude/settings.local.json如果被追踪了也要加进.gitignore。
2.2 写进 ~/.claude/settings.json 的 env 段
这是长期方案,改一次,之后每次启动claude都生效:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "YOUR_MODEL_ID" } }三点提醒:ANTHROPIC_BASE_URL的值就是https://taotoken.net/api,不要自作主张补/v1;ANTHROPIC_AUTH_TOKEN放的才是 Key,不要写成别的变量名;ANTHROPIC_MODEL必须和模型广场里的 ID 完全一致,大小写和连字符都算数。
写完之后,把 shell 里遗留的export ANTHROPIC_BASE_URL=...注释掉或者删掉,然后重开一个终端。理由很简单:两个来源同时存在时,你没法从/status一眼判断哪个赢了。
2.3 临时用环境变量覆盖一次
只想在这次会话里试一下,不想动配置文件,就在启动前 export:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="YOUR_API_KEY" export ANTHROPIC_MODEL="YOUR_MODEL_ID" claudeWindows 的 PowerShell 写法不一样,注意变量名前面的$env::
$env:ANTHROPIC_BASE_URL = "https://taotoken.net/api" $env:ANTHROPIC_AUTH_TOKEN = "YOUR_API_KEY" $env:ANTHROPIC_MODEL = "YOUR_MODEL_ID" claude临时方案的代价是:关掉终端就没了,下次启动又回到旧地址。所以验证阶段可以用它快速试错,但确认没问题后,记得落到settings.json里。另外还有个更省事的办法,适合不想手动写 JSON 的人:
npm install -g @taotoken/taotoken taotoken cc -k YOUR_API_KEY -u https://taotoken.net/api -m YOUR_MODEL_ID注意-u后面跟的还是https://taotoken.net/api,不要加/v1,也不要往这个参数上挂任何 UTM 参数——那是给网页链接用的,不是给工具配置用的。
3. 改完回到 /status:模型名、Base URL、会话统计逐项对
配置改完不等于配通。这一步是排障的关键:/status是唯一的现场证据,别看文件、看它。
3.1 三个字段分别该长什么样
重开终端,进任意目录敲claude,然后输入/status。逐项核对:
- Base URL:应该是
https://taotoken.net/api。如果你还看到https://api.deepseek.com/anthropic,说明旧配置的某一处没清掉,回到第 1 节重新照一遍。 - 模型:应该等于你填进
ANTHROPIC_MODEL的那个 ID,和模型广场里的写法完全一致。名字长得像但差一个后缀,也说明填错了。 - 会话统计:token 计数、消息轮次这些是辅助项。刚启动时数字很小是正常的,发一条消息后再看它有没有涨,能涨说明请求真的走通了。
最直接的验证方式:用同一把 Key 去 TaoToken 模型对话 发一条测试消息。那边通了,说明 Key 和模型 ID 没问题;那边不通,就先解决 Key 的问题,别在 Claude Code 里瞎改。
3.2 三种典型报错,对照着看
| 现象 | 大概率原因 | 怎么处理 |
|---|---|---|
| 401 未授权 | Key 没填、填错、或者带了多余空格和引号 | 重新从 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 复制一次,注意别把换行符带进去 |
| 404 找不到路径 | Base URL 末尾多写了/v1,或者路径被重复拼接 | 把值改回https://taotoken.net/api,末尾不加任何东西 |
/status仍显示旧地址 | shell 里的export还在、或者存在项目级 settings 覆盖 | 注释掉 export,检查.claude/settings.local.json,重开终端 |
还有一种不像报错的「报错」:模型能回答,但答得又慢又怪。这通常是模型 ID 填错了,落到了列表里另一个同系列的模型上。回模型广场对一遍名字,比看日志快。
3.3 改完还不对,按这个顺序再排一遍
先unset ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN ANTHROPIC_MODEL把当前 shell 清干净,再cat ~/.claude/settings.json确认文件内容就是你想的那样,再type claude确认没有别名插手,最后重开终端。这一套下来基本没有排查不到的情况——它一共就四个来源,你全看过了。
顺带说一句:Claude Code 文档里对这几个环境变量的说明更细,接入文档 里有完整对照,遇到变量名拿不准的时候直接翻那边,比在会话里反复试快得多。
4. 通道通了再生成 my-todo-api:src/index.js 与 src/routes/todos.js
/status正常之后,后面就是原汁原味的 Claude Code 实战了。这里要分清一件事:模型通道负责「把话送到模型、把结果拿回来」,它不参与生成业务代码。src/index.js里的 Express 配置、src/routes/todos.js里的 CRUD 逻辑,都是模型根据你的描述写出来的,通道只保证这些内容能正常往返。
4.1 用三要素提示词描述需求
新建一个空目录,进去启动会话:
mkdir my-todo-api cd my-todo-api claude然后在对话框里描述需求。写提示词把握三个要素:上下文(技术栈、项目背景)、约束(不要什么)、期望输出(文件结构长什么样)。照着这个骨架写:
请帮我创建一个 Node.js + Express 的 Todo REST API 项目。 要求: - 提供 GET /todos、POST /todos、PUT /todos/:id、DELETE /todos/:id - 数据存在内存数组里,不接数据库 - 每个 todo 有 id、title、completed、createdAt 四个字段 - 请求和响应都用 JSON,错误统一返回 { "error": "..." } - 生成 README.md,写明启动和 curl 测试步骤 项目结构: my-todo-api/ src/index.js src/routes/todos.js package.json README.md描述完,Claude Code 会先列出「准备创建哪些文件」,等你在权限提示里按y确认。这一步别嫌烦,它就是这个工具的安全边界——在你点头之前,它不会写盘、不会执行命令。
4.2 生成出来的两个核心文件长什么样
package.json大致是这样:
{ "name": "my-todo-api", "version": "1.0.0", "main": "src/index.js", "scripts": { "start": "node src/index.js", "dev": "nodemon src/index.js" }, "dependencies": { "express": "^4.19.2" }, "devDependencies": { "nodemon": "^3.1.0" } }入口文件src/index.js,只做应用装配和监听:
const express = require('express'); const todoRouter = require('./routes/todos'); const app = express(); app.use(express.json()); app.use('/todos', todoRouter); app.get('/', (req, res) => { res.json({ service: 'my-todo-api', status: 'ok' }); }); const port = process.env.PORT || 3000; app.listen(port, () => console.log(`listening on http://localhost:${port}`));路由文件src/routes/todos.js,业务逻辑集中在这里:
const express = require('express'); const router = express.Router(); let items = []; let seq = 1; router.get('/', (req, res) => { const { completed, sort, order } = req.query; let list = items.slice(); if (completed === 'true' || completed === 'false') { list = list.filter((t) => String(t.completed) === completed); } if (sort === 'createdAt') { list.sort((a, b) => order === 'desc' ? b.createdAt.localeCompare(a.createdAt) : a.createdAt.localeCompare(b.createdAt) ); } res.json(list); }); router.post('/', (req, res) => { const title = typeof req.body.title === 'string' ? req.body.title.trim() : ''; if (!title) return res.status(400).json({ error: 'title is required' }); const todo = { id: seq++, title, completed: false, createdAt: new Date().toISOString() }; items.push(todo); res.status(201).json(todo); }); router.put('/:id', (req, res) => { const id = Number(req.params.id); if (!Number.isInteger(id)) return res.status(400).json({ error: 'id must be an integer' }); const todo = items.find((t) => t.id === id); if (!todo) return res.status(404).json({ error: `todo ${id} not found` }); if (typeof req.body.title === 'string') todo.title = req.body.title.trim(); if (typeof req.body.completed === 'boolean') todo.completed = req.body.completed; res.json(todo); }); router.delete('/:id', (req, res) => { const id = Number(req.params.id); const idx = items.findIndex((t) => t.id === id); if (idx === -1) return res.status(404).json({ error: `todo ${id} not found` }); items.splice(idx, 1); res.status(204).end(); }); module.exports = router;生成完别急着跑。继续追问几句:POST /todos传数字类型的 title 会怎样?PUT能不能同时改 title 和 completed?id不是数字时返回什么?让它把边界情况讲清楚,再决定要不要改。「生成 → 质疑 → 修正」这个循环,比一次生成然后跑挂强太多。
4.3 本地安装、启动与 curl 验证
依赖安装和启动由你在本地终端执行,Claude Code 也可以代跑,但每一步都会弹权限确认:
npm install npm start看到listening on http://localhost:3000之后,另开一个终端打接口。注意这些命令是在你自己机器上跑的,Claude Code 只是帮你把命令拼出来:
curl -X POST http://localhost:3000/todos -H "Content-Type: application/json" -d '{"title":"first task"}' curl -X POST http://localhost:3000/todos -H "Content-Type: application/json" -d '{"title":"second task"}' curl http://localhost:3000/todos curl -X PUT http://localhost:3000/todos/1 -H "Content-Type: application/json" -d '{"completed":true}' curl -X DELETE http://localhost:3000/todos/2 curl http://localhost:3000/todos最后一条应该只剩 id 为 1 的记录,且completed是true。如果POST返回 400、PUT返回 404,先把报错原样贴回会话,让 Claude Code 对着src/routes/todos.js解释是哪一段判断拦住了——这比你自己盯着代码猜快。
5. CLAUDE.md 里补一节「模型通道」,别让下一个会话又踩回去
Todo API 跑起来只是这一轮的事。真正省时间的,是把「这个项目怎么配、用什么通道、代码怎么写」写进项目根目录的CLAUDE.md。Claude Code 每次启动都是白纸一张,它不会记得上一轮会话说过什么,但会自动读取根目录的CLAUDE.md。
5.1 模板里加一节环境约定
# 项目说明 ## 项目概述 Node.js + Express 的 Todo REST API。数据暂存在内存数组,后续迁移 SQLite。 ## 技术栈 - 运行时:Node.js 18+ - 框架:Express 4.x - 代码风格:ESLint + Prettier ## 目录结构 src/ index.js # 应用装配与监听 routes/ todos.js # Todo 全部业务逻辑 ## 模型通道 - Base URL:https://taotoken.net/api(末尾不加 /v1) - Key:从控制台创建,写进 ~/.claude/settings.json 的 env 段,不提交进仓库 - 模型 ID:以模型广场当时列表为准 - 排障第一步:进入会话先敲 /status,核对 Base URL 与模型名 ## 命名规范 - 文件名 kebab-case,变量与函数 camelCase,常量 UPPER_SNAKE_CASE - 路由文件按资源名命名,一个文件只管一种资源 ## 重要约定 - 所有响应为 JSON,错误统一 { "error": "..." } - 状态码要准:创建成功 201,删除成功 204 - 路由文件里不直接操作持久化层 ## 禁止事项 - 不用 var,只用 const / let - 不用回调风格,统一 async / await - 不引入 lodash,原生方法够用 ## 启动方式 npm start npm run dev把「模型通道」单独列一节的好处很直接:新人 clone 下来,AI 一启动就知道该往哪指,不会再把 Base URL 写成别的地址,也不用在群里问「你的/status显示的是什么」。
5.2 什么时候更新它
| 时机 | 改哪一节 |
|---|---|
| 换了模型或通道 | 「模型通道」 |
| 引入新依赖 | 「技术栈」 |
| 目录结构调整 | 「目录结构」 |
| 定了新规范 | 「重要约定」 |
| AI 反复犯同一个错 | 「禁止事项」 |
懒得手写就让它自己整理:把当前文件结构和这几轮对话丢过去,让它生成一份CLAUDE.md,你再逐条改成项目真实情况。写完提交进 Git,团队里所有人看到的规范就是同一份。
6. 去控制台核一下这次 Claude Code 的调用
/status正常、接口能跑,说明这一轮从「地址指错」到「模型通道可用」的闭环已经走完了。最后回到 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 看一眼调用记录:刚才那几次生成src/routes/todos.js、解释 404 的请求,是不是都记在账上了。如果一条都没有,说明请求其实没走到通道,/status显示正常也可能是缓存,值得再排一遍。
想长期用它写代码,可以打开 Coding Plan 看看套餐是否够用;需要补一把新 Key 就去 控制台 API Keys 创建;换机器或者换工具时,照着 Claude Code 接入文档 把环境变量再对一遍。用同一把 Key 先在 模型对话 里发一条消息,确认模型 ID 没写错,再去改settings.json,能省掉大半来回折腾。