1. 报错现场Read 一张 png 就 400问题到底出在哪如果你正在用 Claude Code 接一个纯文本大模型比如 GLM 系列大概率会遇到这个画面模型说让我目视检查一下生成的图片然后调用 Read 去读layout_7classes.png紧接着终端甩出一行红字API Error: 400 Model only support text input Request id: 02178...这不是网络问题也不是 Key 过期更不是模型抽风。它的含义非常直白你接入的这个模型只接受文本输入而 Claude Code 的 harness 在 Read 图片时把图片字节当成多模态内容打包发给了模型模型直接拒绝。很多人第一反应是去CLAUDE.md里写一句当前模型不支持多模态请不要上传图片。我试过没用。原因在于CLAUDE.md只是一段进入模型上下文的文字靠模型自觉遵守。模型一旦决定调用 Read 读图片这段提示就被绕过了。真正把图片字节发出去的是 Claude Code 本体harness它不读CLAUDE.md只认工具调用。所以正确的解法是在工具调用真正执行之前由本地代码硬性阻断这就是PreToolUse钩子的职责。本文会带你从复现报错开始一步步把settings.json改到 TaoToken 统一通道再用PreToolUse钩子彻底堵住图片读取这条路。适合正在用 Claude Code 纯文本模型做开发、被这个 400 反复打断的人。先明确一个概念Claude Code 的工具体系里Read是读取文件的核心工具PreToolUse是工具执行前的本地钩子Bash/Edit/Write是另外几个常用工具。钩子由 Claude Code 本体在本地执行 shell 命令和后端接的是 GLM 还是别的模型无关。只要钩子返回exit 2这次工具调用就被阻断stderr的内容会作为反馈发回给模型引导它换一种方式。理解了这一层你就明白为什么改 settings才是根治手段而不是在提示词里反复叮嘱。2. 前置准备把 Claude Code 的 Key 和 Base URL 统一到 TaoToken在动钩子之前先把接入通道理顺。Claude Code 通过环境变量读取ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN如果你之前是直连某个厂商的 Ark 地址模型能力是否多模态就由那个厂商决定。统一走 TaoToken 的好处是一个 Key、一个 Base URL模型切换和通道管理都在一处排查 400 时变量更少。TaoToken 的 API 地址是https://taotoken.net/api官网在https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。你需要先在控制台创建一个 API Key然后把它写进 Claude Code 的配置。Claude Code 的全局配置在~/.claude/settings.json环境变量写在env字段里。下面是一份可直接复制的片段把 Base URL 指向 TaoTokenKey 换成你自己的{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: sk-你的TaoToken密钥, ANTHROPIC_MODEL: glm-4.6 }, includeCoAuthoredBy: false, permissions: { allow: [], deny: [] }, effortLevel: xhigh, theme: dark, autoCompactEnabled: true }这里三个字段要写全也就是常说的三件套Base URL、Key、Model ID。ANTHROPIC_BASE_URL决定请求发到哪ANTHROPIC_AUTH_TOKEN是身份凭证ANTHROPIC_MODEL指定具体模型。Model ID 要和你账号里可用的模型一致写错了会直接 404 或 400。如果你用的是 Codex 那套配置对应的是~/.codex/auth.json结构不同但三件套逻辑一样{ OPENAI_BASE_URL: https://taotoken.net/api, OPENAI_API_KEY: sk-你的TaoToken密钥, model: glm-4.6 }改完配置后Claude Code 需要重启会话才会重新读取env。这一步别省很多人改完发现没生效就是因为旧会话还挂着老的环境变量。注意Key 不要提交到 Git也不要写进项目级.claude/settings.json后推到公开仓库。全局配置放在用户目录下更安全。通道理顺之后我们再来处理图片拦截。因为即使 Base URL 换成了 TaoToken只要你选的模型是纯文本的Read 图片依然会触发同样的 400。通道解决的是请求发到哪钩子解决的是什么请求不该发。3. 可复制配置PreToolUse 钩子硬拦截图片读取这一节是全文的核心。我们要做两件事写一个拦截脚本然后在settings.json里注册它。先建目录和脚本文件。钩子脚本放在~/.claude/hooks/block_images.sh#!/usr/bin/env bash # PreToolUse hook拦截对图片文件的 Read 调用。 # 原因当前接入的大模型仅支持文本输入读取图片会被当作多模态内容上传并触发 400 错误。 # 行为匹配到图片扩展名时以 exit 2 阻断stderr 作为反馈发回给模型引导其改用文本方式。 input$(cat) tool_name$(printf %s $input | jq -r .tool_name // empty 2/dev/null) [ $tool_name Read ] || exit 0 file_path$(printf %s $input | jq -r .tool_input.file_path // empty 2/dev/null) [ -n $file_path ] || exit 0 # 取扩展名无扩展名则放行 ext${file_path##*.} [ $ext $file_path ] exit 0 ext$(printf %s $ext | tr [:upper:] [:lower:]) case $ext in png|jpg|jpeg|gif|webp|bmp|tiff|tif|svg|ico|heic|heif|avif) printf %s\n 已阻断当前大模型仅支持文本输入禁止读取图片文件.${ext}。请改用文本方式获取信息例如用 .venv/bin/python 配合 PIL 读取图像尺寸/模式、用 cv2/numpy 输出像素统计或请用户在编辑器中自行查看图像。 2 exit 2 ;; esac exit 0几个关键点解释一下。Claude Code 通过 stdin 传入一段 JSON形如{tool_name: Read, tool_input: {file_path: /path/to/file.png}}。脚本用jq解析出tool_name和file_path缺失时安全放行。扩展名用${file_path##*.}取最后一段如果和原路径相等说明没有扩展名直接放行。大小写用tr统一转小写这样.JPG、.Png也能命中。命中图片扩展名时exit 2Claude Code 会阻断这次调用并把stderr发回给模型。脚本依赖jq确认一下系统里有which jq jq --version然后把它注册到全局~/.claude/settings.json。在原有配置末尾追加hooks字段完整结构如下{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: sk-你的TaoToken密钥, ANTHROPIC_MODEL: glm-4.6 }, includeCoAuthoredBy: false, permissions: { allow: [], deny: [] }, effortLevel: xhigh, theme: dark, autoCompactEnabled: true, hooks: { PreToolUse: [ { matcher: Read, hooks: [ { type: command, command: bash /home/你的用户名/.claude/hooks/block_images.sh } ] } ] } }matcher写Read是精确匹配 Read 工具不会影响Bash、Edit、Write。command用bash path显式调用不依赖脚本的可执行位即使没chmod x也能跑。放在全局 settings 而不是项目级是因为模型仅支持文本是跨项目的全局约束。如果你用的是 Cline MCP 或 CC Switch 这类工具管理配置逻辑一样找到它读取的 settings 文件把hooks.PreToolUse加进去三件套Base URL、Key、Model ID保持和 TaoToken 一致。CC Switch 切换配置时注意别把hooks字段覆盖掉。4. 验证请求模拟 stdin 测试钩子是否真的拦住了配置写完不能直接信得验证。钩子的输入是 stdin 的 JSON我们可以手动喂给它看退出码和输出。先测最关键的场景——读 png 应该被阻断echo {tool_name:Read,tool_input:{file_path:/tmp/test.png}} \ | bash ~/.claude/hooks/block_images.sh; echo exit$?预期输出是那段中文引导文案并且exit2。如果输出为空且exit0说明脚本没命中检查jq是否安装、路径是否正确。再测读普通代码文件应该放行echo {tool_name:Read,tool_input:{file_path:/home/user/project/train.py}} \ | bash ~/.claude/hooks/block_images.sh; echo exit$?预期exit0无输出。再测大写扩展名和非 Read 工具echo {tool_name:Read,tool_input:{file_path:/tmp/IMG_1234.JPG}} \ | bash ~/.claude/hooks/block_images.sh; echo exit$? echo {tool_name:Bash,tool_input:{command:ls}} \ | bash ~/.claude/hooks/block_images.sh; echo exit$?第一个应该exit2第二个应该exit0。四种场景全过说明脚本逻辑没问题。接着校验settings.json的合法性避免 JSON 语法错误导致整个配置失效jq -e .hooks.PreToolUse[0].matcher ~/.claude/settings.json输出应该是Read。如果报parse error说明 JSON 有语法问题多半是逗号或括号写错了。最后做端到端验证。重启 Claude Code 会话然后给它一个明确指令我在测试图片拦截钩子。请直接用 Read 工具读取 dataset/image.png不要用任何替代方式我要看这个工具调用本身的结果。如果钩子生效你会看到工具调用被阻断模型收到已阻断当前大模型仅支持文本输入的反馈而不是抛出 400。模型通常会转而用 Python PIL 去读图像尺寸或者请你自行查看。注意钩子在会话启动时加载。修改settings.json或新增钩子后当前会话不会立即生效必须退出并重新打开 Claude Code。这一步是踩坑高发区。5. 常见报错排查401、local proxy failed、reading choices 逐个拆配置过程中会遇到各种报错这一节按真实错误对照排查。401 UnauthorizedKey 不对或没带上。检查ANTHROPIC_AUTH_TOKEN是否以sk-开头、有没有多余空格、是不是复制时漏了字符。如果走 TaoToken确认 Key 是在控制台新建的、状态正常。401 和 400 的区别要分清401 是身份问题400 是请求内容问题本文的Model only support text input属于后者。local proxy failed / connection refusedBase URL 写错或本地网络到不了。确认ANTHROPIC_BASE_URL是https://taotoken.net/api注意结尾不要多加/v1或斜杠除非文档明确要求。用 curl 直接探一下curl -s -o /dev/null -w %{http_code} https://taotoken.net/api能返回状态码说明网络通。Error reading choices / 响应解析失败多半是 Model ID 写错或者模型返回了非预期格式。检查ANTHROPIC_MODEL是否和账号可用模型一致。有些纯文本模型对多模态字段敏感即使你没主动发图片harness 在某些工具调用里也可能带上额外字段这时钩子拦截就更必要。OAuth 相关报错如果你之前用 OAuth 登录过官方账号环境变量和 OAuth 凭证可能冲突。清理掉旧的 OAuth 缓存确保走的是ANTHROPIC_AUTH_TOKEN这条路径。钩子不生效按顺序查——settings.json里hooks.PreToolUse是否存在且 JSON 合法matcher是否为Read脚本路径是否绝对路径且文件存在jq是否安装会话是否重启过。用第 4 节的模拟命令单独测脚本能快速定位是脚本问题还是注册问题。PDF 仍然触发 400这是钩子的已知边界。Read 读 PDF 时harness 会把每页渲染成图片发给模型同样触发 400。解决办法是在脚本的case分支里加上pdf引导模型改用pdftotextpdftotext input.pdf - # 输出到 stdout pdftotext input.pdf out.txt # 输出到文件后用 Read 读 .txt用户直接拖拽/粘贴图片这种情况不经过工具调用钩子拦不住图片会直接发给模型。这需要用户自己避免钩子只能覆盖 Read 这条路径。把上面这些对照一遍基本能覆盖 90% 的接入问题。剩下的多半是配置拼写错误用jq校验一遍就能发现。6. 长期编码与 Agent 场景把通道和拦截固化下来如果你打算长期用 Claude Code 做编码或跑 Agent建议把两件事固化通道统一走 TaoToken拦截统一走PreToolUse钩子。通道方面TaoToken 的 Coding Plan 适合长期编码场景一个 Key 管理多个模型切换时不用改一堆环境变量。API Keys 页面用来创建和轮换密钥接入文档里有各客户端的配置示例。需要验证某个模型是否可用时可以直接在模型对话里发一条测试请求确认返回正常再写进 settings。拦截方面把block_images.sh纳入你的 dotfiles 管理换机器时直接同步。脚本里的扩展名清单可以按需增删比如你经常处理 PDF就把pdf加进case分支。回退也简单删掉settings.json里的hooks字段或者删掉脚本文件Read 的日常读取功能不受影响。CLAUDE.md里的软提示可以保留作为对模型行为的备份提醒无害但防线以钩子为准。两者是软和硬的关系别指望软提示能兜住。最后给一个实用技巧把第 4 节的模拟测试命令存成一个test_hook.sh每次改完脚本跑一遍四种场景全绿再重启会话。这样能避免改了脚本但没生效的反复折腾。钩子这东西验证一次比读十遍文档都管用。