1. 长运行任务为什么总在 settings.json 上翻车
Claude Code 在 long-running application development 场景里跑长任务,最容易出问题的不是模型能力,而是 Harness design 没搭好。Harness design 说白了就是给 Claude Code 套一层"线束":权限怎么给、超时怎么设、日志往哪写、会话怎么续。这些全部落在settings.json里。你如果只把 Claude Code 当聊天窗口用,跑个十分钟的小脚本没问题;但一旦让它连续跑几小时去构建一个全栈应用,权限弹窗会打断它、默认超时会掐死它、日志缺失会让你根本不知道它卡在哪一步。
我试过用默认配置跑一个需要多轮迭代的前端生成任务,结果 Claude Code 在第 40 分钟左右因为一次 Bash 权限确认没人点,整个会话挂起,前面的上下文全白费。这就是典型的 Harness design 缺失:模型本身没问题,是外围的配置骨架没给它留出长跑的跑道。
这篇要解决的就是这件事。我会给出一份可以直接复制的settings.json骨架,覆盖权限白名单、超时控制、日志落盘三个核心字段,然后带你用一次真实的长任务运行去验证配置是否生效。适合已经在用 Claude Code、准备把它推进到长运行应用开发场景的开发者。读完你能拿到一份可用的配置模板,以及一套验证动作,知道每一步该看哪个文件、哪个日志、哪个返回码。
需要说明的是,Claude Code 的模型调用需要 API 凭证。我这边用的是 TaoToken 提供的接入方式,它的 API 地址是https://taotoken.net/api,兼容 Anthropic 的接口格式,配置起来和官方 SDK 的写法一致。下面所有配置示例都基于这个接入点,你可以直接替换成自己的凭证。
2. 前置准备:TaoToken 接入与 Claude Code 环境
在动settings.json之前,先把接入层理顺。Claude Code 通过环境变量读取 API 端点和密钥,所以你要先拿到一个可用的 Key,再把它写进环境。
2.1 获取 API Key
打开 TaoToken 的控制台,进入 API Keys 页面创建一个新 Key。创建时注意两点:一是给它起一个能识别用途的名字,比如claude-code-longrun,方便后面在日志里区分;二是记下创建后一次性展示的完整 Key,页面刷新后就看不到了。
拿到 Key 之后,把它写进 shell 的环境变量。我习惯放在~/.zshrc或~/.bashrc里,这样每个新终端都能读到:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="sk-你的Key"写完执行source ~/.zshrc让配置生效,然后用一条最简单的请求验证接入是否通:
curl -s https://taotoken.net/api/v1/messages \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "max_tokens": 64, "messages": [{"role": "user", "content": "reply with ok"}] }'如果返回里带content字段且文本是ok,说明接入层没问题。这一步别跳过,因为后面settings.json里的所有配置都建立在接入可用的前提上,接入不通的话你排查半天配置也是白搭。
2.2 确认 Claude Code 版本与配置目录
Claude Code 的配置分两层:用户级配置在~/.claude/settings.json,项目级配置在项目根目录的.claude/settings.json。长运行任务我建议用项目级配置,因为不同项目的权限需求不一样,混在用户级里容易互相污染。
先确认版本,老版本的字段名和新版本有差异:
claude --version然后确认配置目录存在:
ls -la .claude/ 2>/dev/null || mkdir -p .claude && echo "created"目录建好后,我们就可以往里写骨架了。
3. 可复制的 settings.json 配置骨架
这一节是全文的核心。我把骨架拆成权限、超时、日志三块讲,每块给出字段含义和取值理由,最后拼成一份完整文件。
3.1 权限字段:让长任务不被弹窗打断
长运行任务最怕的就是中途停下来等人确认。permissions字段就是干这个的。它的结构是allow和deny两个数组,allow里的工具调用不会触发确认,deny里的直接拒绝。
{ "permissions": { "allow": [ "Bash(git status)", "Bash(git diff:*)", "Bash(git add:*)", "Bash(git commit:*)", "Bash(npm run:*)", "Bash(npm install:*)", "Bash(pytest:*)", "Bash(python:*)", "Read(*)", "Write(src/**)", "Edit(src/**)" ], "deny": [ "Bash(rm -rf:*)", "Bash(curl:* | sh)", "Read(.env)", "Read(**/secrets/**)" ] } }这里有几个设计取舍值得说。allow里我用了Bash(git diff:*)这种带冒号的写法,冒号后面的*表示匹配该命令的任意参数,这样git diff HEAD~1和git diff main都能放行,不用一条条列。Write和Edit我限定在src/**下,是为了防止长任务跑偏去改配置文件或依赖锁文件。
deny里必须放rm -rf和管道执行远程脚本这两类,长任务里模型一旦判断失误执行了这类命令,损失是不可逆的。.env和 secrets 目录也要挡住,避免密钥被读进上下文再写进日志。
注意:
allow的匹配是前缀匹配,Bash(python:*)会放行python -c "..."这种任意代码执行。如果你的长任务不需要跑任意 Python,把它收窄成Bash(python -m pytest:*)更安全。
3.2 超时字段:给长任务留足跑道
默认超时对长任务来说太短。timeout相关字段控制单次工具调用的等待上限和整个会话的空闲上限。
{ "timeout": { "toolCallMs": 600000, "sessionIdleMs": 1800000, "bashDefaultMs": 300000 } }toolCallMs设成 600000(10 分钟),是因为长任务里一次npm install或一次全量测试跑几分钟很正常,默认值会在中途掐断。sessionIdleMs设成 1800000(30 分钟),给的是会话空闲容忍度,模型在思考或等待外部进程时不会因为短暂无输出被判死。bashDefaultMs是 Bash 命令的默认上限,5 分钟覆盖大多数构建和测试。
这三个值不要盲目调大。toolCallMs调到一小时以上,一旦某个命令真的卡死,你要等一小时才能拿到失败信号。我的经验是先用 10 分钟跑一轮,看日志里有没有接近上限的调用,再决定是否上调。
3.3 日志字段:让长任务可观测
长任务跑起来之后,你看不到中间过程就等于盲跑。logging字段把关键事件落盘,出问题时能回溯。
{ "logging": { "level": "info", "file": ".claude/logs/session.log", "rotate": { "maxSizeMb": 50, "maxFiles": 5 }, "includeToolCalls": true, "includeToolResults": false } }level用info就够,debug在长任务里会产生巨量日志拖慢 IO。file指向项目内的.claude/logs/,方便和代码一起管理。rotate防止单个日志文件无限增长,50MB 一个、保留 5 个,足够覆盖一次几小时的长任务。
includeToolCalls开、includeToolResults关,是个折中:你能看到模型调了什么工具、传了什么参数,但不会把每次工具返回的大段内容都写进去。排查"模型为什么走了这一步"时,调用记录比返回内容更有用。
3.4 完整骨架文件
把上面三块拼起来,加上模型和会话相关字段,就是完整的settings.json:
{ "model": "claude-sonnet-4-5", "permissions": { "allow": [ "Bash(git status)", "Bash(git diff:*)", "Bash(git add:*)", "Bash(git commit:*)", "Bash(npm run:*)", "Bash(npm install:*)", "Bash(pytest:*)", "Read(*)", "Write(src/**)", "Edit(src/**)" ], "deny": [ "Bash(rm -rf:*)", "Bash(curl:* | sh)", "Read(.env)", "Read(**/secrets/**)" ] }, "timeout": { "toolCallMs": 600000, "sessionIdleMs": 1800000, "bashDefaultMs": 300000 }, "logging": { "level": "info", "file": ".claude/logs/session.log", "rotate": { "maxSizeMb": 50, "maxFiles": 5 }, "includeToolCalls": true, "includeToolResults": false }, "context": { "autoCompact": true, "compactThreshold": 0.85 } }context这块是给长任务续命用的。autoCompact开启后,上下文接近窗口上限时自动压缩历史,compactThreshold设成 0.85 表示用到 85% 就开始压。长运行应用开发动辄几小时,不压缩的话上下文早就爆了。
把这份文件写到.claude/settings.json,然后进入验证环节。
4. 验证配置生效:一次长任务运行
配置写完不代表生效。这一节带你用一次真实的长任务,逐项确认权限、超时、日志三个字段都按预期工作。
4.1 构造一个会触发多轮工具调用的任务
验证任务要足够长,能触发权限、超时、日志三条路径。我用一个"生成并测试一个小型 Python 包"的任务来验证,它会依次触发 Write、Bash(pytest)、Bash(git commit):
claude -p "在 src/ 下创建一个 Python 包 mycalc,包含 add 和 divide 两个函数,divide 要对除零抛 ValueError。然后写 pytest 测试覆盖正常和异常路径,跑通测试后 git add 并 commit。"这条命令用-p进入非交互模式,正好模拟长任务无人值守的场景。如果权限配置没生效,它会在第一次 Write 或 Bash 时停下来等确认,任务直接卡住。
4.2 检查权限是否放行
任务跑起来后,另开一个终端看日志:
tail -f .claude/logs/session.log日志里应该出现类似这样的记录:
{"ts":"2025-01-15T10:23:11Z","event":"tool_call","tool":"Write","args":{"path":"src/mycalc/__init__.py"},"permission":"allowed"} {"ts":"2025-01-15T10:23:45Z","event":"tool_call","tool":"Bash","args":{"command":"pytest tests/"},"permission":"allowed"}关键是"permission":"allowed"这个字段。如果看到"permission":"prompted",说明你的allow规则没匹配上,需要回去检查命令写法。比如pytest tests/要能被Bash(pytest:*)匹配,如果你写的是Bash(pytest tests/:*)就匹配不到带其他参数的调用。
4.3 检查超时是否按预期工作
超时不好直接观察,但可以通过一个故意跑长的命令来验证。在任务里加一步sleep:
claude -p "执行 bash -c 'sleep 400',然后告诉我完成了。"sleep 400是 400 秒,小于bashDefaultMs的 300000 毫秒(300 秒)?不对,400 秒大于 300 秒,所以这条命令应该被超时掐断。日志里会出现:
{"ts":"2025-01-15T10:30:00Z","event":"tool_result","tool":"Bash","status":"timeout","elapsedMs":300012}看到"status":"timeout"且elapsedMs接近 300000,说明bashDefaultMs生效了。如果你希望这类长命令能跑完,就把它挪到一个单独的allow规则里并配更长的超时,而不是全局调大bashDefaultMs。
4.4 检查日志轮转与上下文压缩
长任务跑完后,确认日志文件按预期生成和轮转:
ls -lh .claude/logs/应该看到session.log以及可能的session.log.1等轮转文件。如果单次任务就产生了超过 50MB 的日志,说明includeToolResults可能被误开了,回去关掉。
上下文压缩的验证看日志里的 compact 事件:
grep "compact" .claude/logs/session.log出现{"event":"context_compact","beforeTokens":180000,"afterTokens":45000}这样的记录,说明autoCompact在上下文接近上限时触发了压缩,长任务能继续跑下去。
5. 本篇常见错误排查
配置跑不通时,按下面这几类对照排查,基本能覆盖九成问题。
5.1 权限规则不匹配导致任务卡住
最常见的现象是任务跑一半不动了,日志停在某个tool_call没有后续。这通常是allow规则没匹配上。Claude Code 的权限匹配是精确到命令前缀的,Bash(npm run:*)能匹配npm run build,但匹配不了npx npm run build。排查方法是把日志里那条tool_call的args.command复制出来,和你的allow规则逐条比对。
另一个坑是Write(src/**)这种 glob 写法。不同版本对**的支持不一致,保险起见可以写成Write(src/*)加Write(src/**/*)两条,覆盖一层和深层目录。
5.2 超时字段名写错导致不生效
timeout下的字段名在不同版本里改过。老版本用toolTimeout,新版本用toolCallMs。如果你写了老字段名,配置不会报错,但也不会生效,任务还是按默认超时跑。排查方法是启动时加--debug看配置加载日志:
claude --debug -p "test" 2>&1 | grep -i timeout如果输出里没有你配置的值,说明字段名不对,对照当前版本文档改。
5.3 日志文件不生成或为空
日志不生成通常是两个原因:一是logging.file的目录不存在,Claude Code 不会自动创建父目录;二是level设成了error,而正常任务没有 error 事件。先手动建目录:
mkdir -p .claude/logs再把level调成info重跑。如果日志生成了但内容为空,检查includeToolCalls是否为true,设成false的话工具调用不会记录,日志里就只剩会话级事件。
5.4 上下文压缩没触发导致任务中断
长任务跑到后面报上下文超限,但日志里没有 compact 事件,说明autoCompact没开或compactThreshold设得太高。compactThreshold是比例值,设成 0.95 意味着用到 95% 才压,留给压缩本身的空间就不够了。建议设在 0.8 到 0.85 之间。另外确认context字段是顶层字段,不要嵌在logging里。
5.5 接入层报错被误判为配置问题
如果任务一开始就报 401 或连接失败,别急着改settings.json,先回到第 2 节的 curl 验证接入。接入不通时,Claude Code 的表现和配置错误很像,都是任务起不来。区分方法是看日志里有没有tool_call事件:有tool_call说明接入是通的,问题在权限或超时;一个tool_call都没有就报错,问题在接入层。
6. 把配置沉淀成可复用的骨架
跑通一次验证之后,建议把这份settings.json抽成模板,按项目类型分几套。前端项目、后端服务、数据脚本的权限需求差别很大,混用一套配置要么放得太宽要么卡得太死。
我自己的做法是在仓库里放一个.claude/settings.template.json,新项目初始化时复制成.claude/settings.json再按需改allow列表。模板里deny和timeout、logging三块保持不动,只调权限白名单。这样既保证安全底线一致,又给不同项目留了灵活度。
另外,长运行任务的成本要心里有数。一次几小时的自主构建,token 消耗可能是短任务的几十倍。建议在settings.json里配好日志之后,定期用日志统计工具调用次数和 token 用量,找到哪些步骤在烧钱。如果发现某类工具调用频繁且收益低,就把它从allow里拿掉,逼模型换更高效的路径。
配置骨架搭好只是 Harness design 的第一步。真正跑长任务时,你还会遇到模型在某个功能上反复迭代、评估环节缺失导致质量下滑等问题。这些需要在骨架之上再加规划器和评估器角色,但那属于下一层的设计,先把这份settings.json跑稳,再往上叠。