1. 推送被拒的真实场景:pre-receive hook 到底在拦什么
你敲下git push origin master,终端没有出现熟悉的Writing objects: 100%,而是甩出一行红字:
! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to 'git@github.com:xxx/xxx.git'这个报错的核心含义是:你的提交在本地完全合法,但服务端的 pre-receive 钩子(hook)在接收前把它拦下来了。pre-receive hook 是 Git 服务端在「接收推送」这个动作真正落库之前执行的最后一道校验脚本,它运行在远端仓库里,而不是你的电脑上。只要它返回非零退出码,整批推送就会被整体拒绝,一个对象都不会写入。
它和另外两个常见钩子的区别值得先理清:
| 钩子 | 运行位置 | 触发时机 | 能否阻止推送 |
|---|---|---|---|
| pre-commit | 本地 | commit 前 | 能,本地拦截 |
| pre-receive | 服务端 | push 接收前 | 能,整批拒绝 |
| update | 服务端 | 每个分支更新前 | 能,按分支拒绝 |
| post-receive | 服务端 | 接收完成后 | 不能,仅通知 |
所以看到pre-receive hook declined,你要排查的方向不在本地 Git 命令写错,而在「远端仓库配置了哪些规则」。最常见的四类拦截原因:
第一类是大文件超限。GitHub 单文件硬上限 100MB,超过直接拒绝;仓库总体积、单次推送体积也有软限制。很多人新建仓库后把数据集、压缩包、模型权重一起git add .,第一次推送就撞墙。
第二类是分支保护规则。master/main 被设为 protected branch 后,禁止 force push、禁止直接推送、要求 PR 审核、要求状态检查通过。你本地直接推 master,服务端 hook 判定不合规。
第三类是提交信息规范。企业 GitLab、Gitea 常配置 commit message 必须匹配^(feat|fix|docs|chore):这类正则,或者必须关联 issue 号(如#123)。信息不合规,hook 拒绝。
第四类是服务端自定义脚本。比如禁止提交.env、禁止提交超过 N 个文件的单次推送、禁止特定用户推送特定分支。
我试过最坑的一种情况:报错信息里只写了pre-receive hook declined,但真正原因藏在服务端返回的上一行或下一行。GitHub 会额外打印remote: error: File xxx is 120.00 MB; this exceeds GitHub's file size limit of 100.00 MB,GitLab 会打印remote: GitLab: You are not allowed to push code to protected branches。先完整读一遍 remote: 开头的每一行,90% 的排查时间能省下来。
这一篇就围绕这个报错,从定位、清理本地缓存、配置规范、到把远端校验相关的 Key/API 通道统一到 TaoToken 做验证推送,给你一套能直接复制的流程。适合刚建仓库就推不上去的新手,也适合被分支保护卡住的老手。
2. TaoToken 前置:把校验通道和 Key 统一起来
在动手清理大文件和改 hook 规则之前,先把「推送验证」这条链路的环境准备好。很多同学排查到一半发现,本地 git 配置、远端 hook、以及调用模型做提交信息规范校验的 API Key 散落在三四个地方,改一处忘一处,验证结果自然对不上。
TaoToken 在这里的角色是统一的 Key/API 通道:你本地脚本、CI 里的提交信息检查、以及需要调用模型生成规范 commit message 的工具,都可以走同一个 Base URL 和同一把 Key,避免「本地能推、CI 拒绝」这种环境不一致导致的假性 hook 报错。
先拿到入口。控制台地址是https://taotoken.net/console,登录后在 API Keys 页面创建一把 Key。创建时注意两点:一是权限范围选最小可用,只勾选你实际需要的模型;二是 Key 只在创建时完整显示一次,复制后立刻存进密码管理器。
拿到 Key 之后,你需要记住三个核心参数,后面所有配置都围绕它们:
- Base URL:
https://taotoken.net/api - API Key:
sk-开头的那串 - Model ID:按你实际调用的模型填,比如
claude-sonnet-4-5或gpt-4o
如果你用的是 Claude Code 这类命令行编码工具,它读取的是环境变量;如果你用的是 Cline、Cursor 这类编辑器插件,它读取的是插件设置里的 Base URL + Key + Model 三件套。无论哪种,三件套必须同时正确,只填 Key 不填 Base URL 是最常见的 401 来源。
这里要强调一个排查思路:pre-receive hook declined本身和 TaoToken 没有直接因果关系,hook 是 Git 服务端的事。但当你需要用模型自动校验提交信息是否符合规范、或者在 CI 里调用模型做代码检查时,通道不统一就会引入新的失败点。把 Key 和 Base URL 收敛到一处,能让「到底是 hook 拒绝还是 API 拒绝」一眼分清。
文档入口在https://taotoken.net/doc,里面有各语言 SDK 的接入示例。建议先跑通一次最小请求,确认 Key 有效,再去处理 Git 推送问题,这样排查链路是干净的。
3. 可复制配置:本地 git 片段与 hook 规则对照
这一节给你能直接粘贴的配置。分三块:清理大文件的本地 git 命令、提交信息规范配置、以及统一 Key 通道的 settings 片段。
3.1 清理已进入缓存的大文件
报错提示大文件名后,直接rm再 commit 是无效的,因为git add已经把大文件写进了本地对象库。必须用filter-branch从历史里彻底移除。假设报错文件是crawler/zk-crawler.rar:
git filter-branch --force --index-filter \ 'git rm -rf --cached --ignore-unmatch crawler/zk-crawler.rar' \ --prune-empty --tag-name-filter cat -- --all执行完再修正提交并强推:
git commit --amend -C HEAD git push origin master --force强推后清理本地残留对象,避免下次又被打包进去:
rm -rf .git/refs/original/ git reflog expire --expire=now --all git gc --prune=now如果你不确定仓库里还有哪些大文件,先扫一遍:
git rev-list --objects --all | \ git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \ awk '/^blob/ {print $3, $4}' | sort -nr | head -20这条命令列出历史中体积最大的 20 个对象,单位是字节。超过 100MB(104857600 字节)的都要处理。
3.2 提交信息规范配置
如果 hook 校验的是 commit message 格式,本地先自查。用 commit-msg 钩子做本地预检,避免推到远端才被拒:
cat > .git/hooks/commit-msg <<'EOF' #!/bin/sh MSG=$(head -1 "$1") PATTERN='^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,}' if ! echo "$MSG" | grep -qE "$PATTERN"; then echo "commit message 不符合规范: $MSG" echo "示例: feat(login): 增加短信验证码" exit 1 fi EOF chmod +x .git/hooks/commit-msg这样不合规的信息在本地就被拦下,不会走到远端 pre-receive。
3.3 统一 Key 通道的 settings 片段
把模型调用的三件套写进项目级配置,CI 和本地共用。以 JSON 为例,放在项目根目录的.taotoken/settings.json:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model_id": "claude-sonnet-4-5", "timeout": 60, "retry": 3 }如果工具读取的是 TOML(比如某些 CLI 的 coding-plan 配置):
[provider] base_url = "https://taotoken.net/api" api_key = "sk-你的Key" model_id = "claude-sonnet-4-5" [request] timeout = 60 retry = 3注意:不要把真实 Key 提交进仓库。把settings.json加进.gitignore,仓库里只放settings.example.json,CI 里用环境变量注入。
3.4 hook 规则对照表
| 报错关键词 | 拦截类型 | 本地应对 |
|---|---|---|
| exceeds ... file size limit | 大文件 | filter-branch 清理 |
| protected branch | 分支保护 | 走 PR 或临时解保护 |
| commit message | 信息规范 | commit-msg 本地预检 |
| not allowed to push | 权限 | 检查账号角色 |
| GH001 / push declined | 体积超限 | 拆分推送 |
对照这张表,先看 remote 输出里出现哪个关键词,再选对应方案,不要盲目强推。
4. 验证请求:确认推送一次性通过
配置改完,进入验证环节。验证分两步:先确认 Key 通道可用,再确认 Git 推送成功。
4.1 验证 Key 通道
用 curl 发一个最小请求,确认 Base URL 和 Key 都对:
curl -s -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的Key" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "messages": [{"role": "user", "content": "回复 OK"}], "max_tokens": 10 }'返回里出现choices数组且内容正常,说明通道没问题。如果返回 401,先查 Key 是否复制完整、是否带了多余空格;如果返回local proxy failed,说明 Base URL 写错或网络出口有问题,检查是不是把https://taotoken.net/api写成了别的路径。
4.2 验证 Git 推送
清理完大文件、确认提交信息合规后,执行推送:
git push origin master成功时你会看到:
Enumerating objects: 42, done. Counting objects: 100% (42/42), done. Delta compression using up to 8 threads Compressing objects: 100% (30/30), done. Writing objects: 100% (42/42), 1.2 MiB | 3.4 MiB/s, done. Total 42 (delta 12), reused 0 (delta 0) To github.com:xxx/xxx.git a1b2c3d..e4f5g6h master -> master最后一行master -> master没有rejected,就是通过了。如果还是被拒,回到第 5 节对照报错。
4.3 用模型辅助生成规范提交信息
如果你想让提交信息自动符合 hook 规范,可以写个小脚本调用模型生成:
#!/bin/bash DIFF=$(git diff --cached --stat) PROMPT="根据以下改动生成一条符合 conventional commits 规范的提交信息,只输出一行:\n$DIFF" curl -s -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_KEY" \ -H "Content-Type: application/json" \ -d "{\"model\":\"claude-sonnet-4-5\",\"messages\":[{\"role\":\"user\",\"content\":\"$PROMPT\"}],\"max_tokens\":50}" \ | grep -o '"content":"[^"]*"' | head -1把TAOTOKEN_KEY设为环境变量,脚本就能复用同一把 Key。这样提交信息规范由模型保证,本地 commit-msg 钩子兜底,远端 pre-receive 自然不会再因格式拒绝。
验证通过后,建议再跑一次git log --oneline -5确认历史干净,没有大文件残留的提交。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
这一节把实际会撞到的报错逐个拆开。注意区分:哪些是 Git hook 的错,哪些是 API 通道的错,别混在一起查。
5.1 401 Unauthorized
出现在 API 调用时,不是 Git 报错。原因通常是:
- Key 复制时带了首尾空格或换行
- Key 已过期或被删除
- 请求头写成了
Authorization: sk-xxx,漏了Bearer - Base URL 和 Key 不属于同一环境
排查:echo -n "sk-你的Key" | wc -c看长度是否和创建时一致;用curl -v看请求头实际发出的内容。
5.2 local proxy failed
这个报错说明请求根本没到达服务端,卡在本地网络出口。常见于:
- Base URL 写成了
https://taotoken.net/api/多了一个斜杠导致路径拼接错误 - 本地设置了 HTTP_PROXY 环境变量指向一个不可用的地址
- 防火墙拦截了出站请求
排查:env | grep -i proxy看有没有残留代理变量;curl -v https://taotoken.net/api看 TCP 连接是否建立。注意,这里说的是排查本地网络配置,不是让你去搭什么通道,把环境变量清干净即可。
5.3 reading choices 相关报错
典型形式是cannot read property 'choices' of undefined或reading 'choices'。这说明代码拿到了响应对象,但响应体里没有choices字段。原因:
- 请求体 JSON 格式错误,服务端返回了错误对象而非正常响应
- model 字段填了不存在的模型名
- 响应被中间层改写
排查:先把原始响应打印出来console.log(JSON.stringify(resp)),看实际返回结构。多数情况是 model ID 拼错。
5.4 OAuth 相关报错
如果你用的是 Claude Code 或类似工具,它可能走 OAuth 流程而非纯 API Key。报错形式如OAuth token expired或invalid_grant。处理方式:
- 重新执行登录命令刷新 token
- 确认工具版本支持当前认证方式
- 如果工具同时支持 API Key 和 OAuth,优先用 API Key 模式,配置更稳定
5.5 Git 侧报错对照
| 报错 | 原因 | 处理 |
|---|---|---|
| pre-receive hook declined | 服务端规则拒绝 | 读 remote 输出定位 |
| failed to push some refs | 远端有新提交 | 先 pull --rebase |
| remote: error: GH001 | 仓库超 1GB | 清理历史大文件 |
| protected branch hook declined | 分支保护 | 走 PR 流程 |
| non-fast-forward | 历史分叉 | 确认后 force push |
排查顺序建议:先看 remote: 开头的完整输出 → 判断是 hook 规则还是网络 → 再决定改本地还是改远端。不要一看到 rejected 就 force push,强推解决不了 hook 规则问题,反而可能丢提交。
6. 把校验链路收敛到统一通道
走到这里,pre-receive hook declined的排查路径已经完整:读 remote 输出定位规则类型,大文件用 filter-branch 清理,提交信息用本地钩子预检,分支保护走 PR,API 通道用统一 Key 验证。
真正让排查变快的,不是记住某一条命令,而是把变量收敛。Git 推送失败的原因可能有三层:本地对象库、远端 hook 规则、以及调用模型做校验时的 API 通道。前两层是 Git 本身的事,第三层如果 Key 和 Base URL 散落各处,就会制造「明明本地能推、CI 却拒绝」的假象。
把模型调用的三件套——Base URLhttps://taotoken.net/api、API Key、Model ID——统一到一处配置,本地脚本和 CI 共用同一把 Key,验证请求时先跑通最小 curl,再去推 Git。这样任何一次失败,你都能立刻判断是 hook 拒绝还是通道拒绝。
需要创建 Key 的话,控制台在https://taotoken.net/console,接入示例在https://taotoken.net/doc。如果你要长期在编码流程里用模型做提交信息规范、代码检查,可以看下 coding-plan 方案,把调用额度固定下来,避免每次临时申请 Key 打断排查节奏。推送通过后,记得git gc收尾,让仓库保持干净。