1. 从一次真实的 git push 被拒说起git push报错pre-receive hook declined是很多人在 GitLab、Gitea 这类自建 Git 服务上都会撞到的一堵墙。它的字面意思是服务端在真正接收你推送的数据之前跑了一个叫pre-receive的钩子脚本而这个脚本拒绝了这次推送。注意拒绝发生在服务端不在你本地所以你改本地配置、换 SSH key、重装 Git 通常都没用得从服务端规则和凭证链路两头查。这个报错能做什么判断它其实是一个「总闸」式的提示背后可能的原因有好几类分支保护规则不允许你强推、你的账号角色权限不够、服务端钩子脚本里有自定义校验比如提交信息格式、文件大小、禁止大文件、凭证过期导致身份识别成了匿名用户。适合谁看适合正在用 GitLab 做团队协作、被 master 或 main 分支保护卡住、又不想盲目删库重来的开发者。我试过最典型的一次本地 rebase 之后历史变了只能强推结果git push origin master --force直接返回! [remote rejected] master - master (pre-receive hook declined)。当时第一反应是权限问题换了 Owner 账号还是不行最后才发现是 master 被设成了 Protected Branch保护分支默认禁止 force push。这个坑很常见但排查顺序如果不对会在权限、SSH、token 之间来回绕。所以这篇不打算只讲「取消保护分支」这一招而是把服务端钩子、分支保护、权限、凭证链路四层拆开逐层给你可复制的排查命令和配置骨架。同时因为现在很多人的开发流里会接入 AI 编码工具Cline、CC Switch 等凭证链路这一层经常和 AI 工具的 Key 配置混在一起我也会给出 TaoToken 统一 Key 通道下的配置片段让 Git 凭证和 AI 工具凭证各归各位不互相污染。先记住一个判断原则pre-receive hook declined是服务端拒绝本地git config改不动它。你要做的是「定位是哪一层拒绝的」而不是「在本地反复重试」。2. 逐层定位钩子、分支保护、权限与凭证链路2.1 先分清 pre-receive 钩子到底拦了什么pre-receive是 Git 服务端在接收推送时执行的第一个钩子它拿到的是「一批待更新的引用」。只要脚本返回非零退出码整批推送全部拒绝客户端就看到pre-receive hook declined。GitLab 自己内置的很多校验分支保护、推送规则 Push Rules就是通过这个钩子实现的所以你看到的报错往往不是自定义脚本而是平台规则。排查第一步先确认是「平台规则」还是「自定义钩子」。如果你有服务器权限去仓库目录看钩子# 进入 GitLab 仓库存储目录路径按你的安装方式调整 cd /var/opt/gitlab/git-data/repositories/hashed # 找到对应仓库的 .git 目录后查看 ls -l hooks/ cat hooks/pre-receive如果hooks/pre-receive是一个指向 GitLab 内部脚本的软链那基本就是平台规则在拦你重点转向分支保护和推送规则。如果是团队自己写的脚本就要看脚本里exit 1的条件常见的有提交信息必须匹配某个正则、禁止提交超过 100MB 的文件、禁止直接推 master。没有服务器权限也没关系用GIT_TRACE_PACKET1看服务端返回的详细信息GIT_TRACE_PACKET1 git push origin master --force 21 | tail -40服务端有时会在remote:前缀的行里给出更具体的原因比如remote: GitLab: You are not allowed to force push code to a protected branch。这一行信息比笼统的pre-receive hook declined值钱得多先把它抓出来。2.2 分支保护master/main 默认不让强推GitLab 里 master 或 main 通常被设为 Protected Branch保护规则默认包含「不允许 force push」和「不允许直接 push」。这就是为什么你换了 Owner 账号还是失败——保护分支的规则对角色也生效除非你显式调整。进入路径Settings - Repository - Protected Branches。你会看到 master 的Allowed to push和Allowed to merge设置。临时方案是 Unprotect master强推成功后立刻重新加回保护。但更稳的做法是不要强推 master而是走 Merge Request。如果你确实需要临时放开操作后记得恢复并且用命令确认保护状态# 查看远程分支保护情况GitLab 需通过 API这里用通用方式确认分支存在 git ls-remote --heads origin注意Unprotect 只是临时手段团队仓库里长期放开 master 保护是高风险操作强推会覆盖别人的提交历史务必先和协作者确认。2.3 权限与角色Developer 推不了保护分支GitLab 的角色从低到高是 Guest、Reporter、Developer、Maintainer、Owner。Developer 可以推非保护分支但推保护分支通常需要 Maintainer 及以上。很多人以为自己是 Owner 就万事大吉其实如果保护规则里Allowed to push设成了「No one」或只允许特定角色Owner 也会被拦。确认自己的角色进入项目Members页面看自己的 Role。如果角色够但还被拦检查保护规则里的Allowed to push是不是被限制成了特定人。这一层和凭证链路容易混淆有时候你以为是权限不够其实是凭证失效服务端把你当成了匿名用户自然没权限。2.4 凭证链路SSH key、token 与 AI 工具 Key 别混用凭证链路是排查里最容易被忽略的一层。git push用的凭证可能是 SSH key也可能是 HTTPS 的 Personal Access Token。如果 token 过期或权限范围scope不含write_repository推送会被拒报错有时也会落到 hook declined 上。先确认当前 remote 用的是哪种协议git remote -v如果是https://检查凭证缓存# 查看 macOS 钥匙串或 Linux 凭证helper git config --get credential.helper如果是git开头测试 SSH 身份ssh -T gityour-gitlab-host返回Welcome to GitLab, yourname说明 SSH 正常返回Permission denied就是 key 没配好。这里要特别提醒现在很多人同时用 Cline、CC Switch 这类 AI 编码工具这些工具也需要配置 API Key。Git 的凭证和 AI 工具的 Key 是两套东西不要因为都在「配置」里就混在一起。下面第 3 节我会给出 TaoToken 统一 Key 通道的配置骨架让 AI 工具的 Key 集中管理Git 凭证单独走 SSH 或 token互不干扰。3. 可复制配置骨架config.toml 与 settings.json这一节给你可以直接抄的配置骨架。核心思路是AI 编码工具Cline、CC Switch统一走 TaoToken 的 API 通道Base URL 指向https://taotoken.net/apiKey 用统一 Key模型 ID 按需填。Git 凭证则单独配置两者不共用同一个 Key 文件。3.1 Cline 的 settings.json 骨架Cline 是 VS Code 里的 AI 编码插件配置通常写在 settings 里。下面是一个可复制的骨架把 Base URL、Key、Model ID 三件套填全{ cline.apiProvider: openai, cline.openAiBaseUrl: https://taotoken.net/api, cline.openAiApiKey: sk-你的TaoToken统一Key, cline.openAiModelId: claude-sonnet-4-20250514, cline.openAiHeaders: { Content-Type: application/json } }三个字段对应关系要记牢Base URL 是https://taotoken.net/apiKey 是你在控制台生成的统一 KeyModel ID 按你实际要用的模型填。如果 Model ID 填错请求会返回模型不存在的错误而不是 hook declined这两类报错要分清。3.2 CC Switch 的 config.toml 骨架CC Switch 用来在多个 API 通道之间切换配置一般放在config.toml。下面这个骨架把 TaoToken 作为一个 provider 写进去# ~/.cc-switch/config.toml default_provider taotoken [providers.taotoken] name TaoToken base_url https://taotoken.net/api api_key sk-你的TaoToken统一Key model claude-sonnet-4-20250514 wire_api chat [providers.taotoken.headers] Content-Type application/jsonwire_api按工具要求填chat或responses不确定时先用chat。base_url结尾不要多加/v1TaoToken 的 API 入口就是https://taotoken.net/api多写路径容易 404。3.3 Git 凭证单独配置别和 AI Key 混用Git 这边如果你用 HTTPS建议用 token 而不是密码# 把 token 写进 remote注意不要提交到仓库 git remote set-url origin https://oauth2:你的GitTokenyour-gitlab-host/group/repo.git更安全的做法是用凭证 helper 缓存而不是把 token 写进 URL。SSH 方式则确认~/.ssh/config里 host 配置正确# ~/.ssh/config Host your-gitlab-host HostName your-gitlab-host User git IdentityFile ~/.ssh/id_ed25519注意AI 工具的 Key 和 Git 的 token 是两套凭证体系。把 TaoToken 的 Key 填进 Git remote 是无效的反之亦然。排查 hook declined 时先确认你用的是哪套凭证。3.4 用环境变量隔离避免串味如果你在 CI 或脚本里同时用 Git 和 AI 工具建议用环境变量隔离export TAOTOKEN_API_KEYsk-你的TaoToken统一Key export GIT_SSH_COMMANDssh -i ~/.ssh/id_ed25519 -o IdentitiesOnlyyes这样 AI 工具读TAOTOKEN_API_KEYGit 走指定 SSH key互不影响。配置骨架给到这里下一节我们用命令验证请求是否真的通了。4. 验证请求与成功结果从复现到恢复推送配置写完不算完得验证。这一节给你复现命令和成功结果的判断标准分「AI 工具通道验证」和「Git 推送验证」两条线。4.1 验证 TaoToken API 通道是否通先用 curl 直接打一次 API确认 Key 和 Base URL 没问题curl -sS https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-20250514, messages: [{role: user, content: ping}], max_tokens: 16 } | head -40成功的话你会看到 JSON 里带choices字段里面有模型返回的内容。如果返回 401说明 Key 不对或没带上如果返回 404多半是路径写错检查是不是多写了/v1或少了。这一步通了说明 AI 工具的通道没问题Cline 和 CC Switch 里填同样的三件套即可。4.2 复现 git push 报错在验证 Git 之前先稳定复现一次报错确认你面对的就是 hook declined# 制造一次需要强推的场景仅测试仓库操作 git commit --amend -m test amend git push origin master --force预期输出! [remote rejected] master - master (pre-receive hook declined) error: failed to push some refs to your-gitlab-host/group/repo.git看到这一行说明服务端确实在 pre-receive 阶段拒绝了。接下来按第 2 节的顺序排查先看GIT_TRACE_PACKET的 remote 提示再查保护分支再查角色权限最后查凭证。4.3 恢复推送的成功结果假设定位到是保护分支问题临时 Unprotect 后重新推送git push origin master --force成功输出应该是Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Writing objects: 100% (3/3), 320 bytes | 320.00 KiB/s, done. Total 3 (delta 1), reused 0 (delta 0) To your-gitlab-host/group/repo.git abc1234...def5678 master - master (forced update)看到forced update就说明推送成功。强推成功后立刻回到Settings - Repository - Protected Branches把 master 重新加回保护这一步千万别忘。如果是权限问题把账号角色升到 Maintainer 或调整保护规则的Allowed to push再推一次即可。如果是凭证问题重新生成带write_repositoryscope 的 token更新 remote 后重推。4.4 验证 AI 工具在推送流程里不添乱如果你用 Cline 或 CC Switch 辅助写提交信息、生成 MR 描述确认这些工具只调用 API不直接操作 Git 凭证。验证方式临时把TAOTOKEN_API_KEY改错AI 工具应该报 401而git push不受影响反过来把 Git token 改错git push报凭证错误AI 工具仍能正常对话。两条链路互不干扰才算配置干净。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节把真实会撞到的报错列出来对照排查。注意区分「AI 工具通道报错」和「Git 推送报错」它们的根因完全不同。5.1 401 Unauthorized出现在 curl 或 AI 工具里说明 Key 无效或没带上。检查三件事Key 是否复制完整有没有多余空格、请求头是否是Authorization: Bearer sk-xxx、Key 是否在控制台被禁用。Git 推送一般不会报 401如果 Git 报 401那是 token 问题不是 hook 问题。5.2 local proxy failed这个报错通常出现在 AI 工具尝试连接 API 时说明本地网络层或代理配置有问题。先确认base_url写的是https://taotoken.net/api没有多余路径再确认本机没有残留的代理环境变量干扰env | grep -i proxy如果有HTTP_PROXY、HTTPS_PROXY指向一个不可用的地址清掉再试unset HTTP_PROXY HTTPS_PROXY ALL_PROXY5.3 reading choices 相关报错类似cannot read property choices of undefined或reading choices说明返回的 JSON 结构里没有choices字段。常见原因是模型 ID 填错导致返回了错误对象、wire_api类型不匹配、或者请求体格式不对。先用 4.1 的 curl 确认原始返回再对照工具要求的字段格式调整。5.4 OAuth 相关报错如果工具走 OAuth 流程报错检查回调地址和 token 是否过期。OAuth 和 API Key 是两种认证方式TaoToken 统一 Key 通道用的是 API Key不需要走 OAuth。如果你在工具里同时开了 OAuth 和 API Key可能互相覆盖建议只保留一种。5.5 对照表报错与根因报错信息出现位置根因处理pre-receive hook declinedgit push分支保护/权限/钩子查保护分支与角色401 UnauthorizedAPI 请求Key 无效检查 Key 与请求头local proxy failedAI 工具代理/网络清代理环境变量reading choicesAI 工具返回结构异常核对 Model ID 与 wire_apiOAuth 报错工具登录认证方式冲突只保留 API Key排查顺序建议先确认报错属于哪条链路Git 还是 AI 工具再按表定位。别把 Git 的 hook declined 当成 Key 问题去改 API 配置方向错了会浪费很多时间。6. 把凭证管好推送和 AI 工具都不打架走到这里pre-receive hook declined的排查路径应该清楚了它是服务端拒绝先抓GIT_TRACE_PACKET的 remote 提示再按分支保护、角色权限、凭证链路逐层查。临时 Unprotect 能救急但长期方案是走 MR、别强推保护分支。配置层面把 AI 工具的 Key 和 Git 凭证分开管理是关键。Cline 的settings.json、CC Switch 的config.toml里填 TaoToken 的三件套Base URLhttps://taotoken.net/api、统一 Key、Model IDGit 走 SSH 或独立 token用环境变量隔离互不串味。这样即使某一层出问题你也能快速判断是哪条链路。如果你还没配好统一 Key 通道可以去控制台生成 Key再对照接入文档把 Cline 或 CC Switch 配起来。配好后用第 4 节的 curl 验证一次确认choices正常返回再回到 Git 推送流程两条线都通开发流就顺了。
阅读完成 · 觉得有帮助?