1. 从一次真实的 git 推送失败说起
git push敲下去,终端回你一句Permission denied (publickey)或者! [rejected] master -> master (fetch first),这大概是每个用 Git 的人都会撞上的墙。它烦人的地方在于:报错信息看起来都差不多,但根因可能完全不在一个层面——有时候是你本地id_rsa.pub根本没配到远端账号里,有时候是 remote 地址写成了 HTTPS 却拿着 SSH 的钥匙去开门,还有时候密钥没问题、权限也没问题,纯粹是远端仓库有你本地没有的提交,Git 拒绝覆盖。
这篇就聚焦「本地 git 推送失败」这一个场景,把整条链路拆成三层来定位:密钥层(id_rsa.pub 是否存在、是否配对)→ 认证层(ssh -T 能不能通)→ 远端层(remote repository 地址与分支状态)。每一层我都给出可以直接复制执行的命令,以及对应的报错长什么样、说明问题卡在哪。你不需要从头读,直接对着自己终端里的报错往下找就行。
适合谁看:刚配好 SSH 准备推第一个仓库的新手;换了电脑、重装系统后密钥对不上的人;以及被hook declined、fetch first这类远端拒绝搞得一头雾水的开发者。全程命令都是实测可跑的,路径以 Windows 为主,macOS/Linux 我会标注差异。
先说结论性的判断顺序,后面每一节展开:先ssh -T测认证通不通,通了再git remote -v看地址对不对,地址对了再看是不是分支落后。这三步能覆盖九成以上的推送失败。下面逐层拆。
2. 密钥层排查:id_rsa.pub 到底配没配对
2.1 先确认本地有没有密钥
SSH 推送的第一道门是本地密钥对。公钥(id_rsa.pub)交给远端,私钥(id_rsa)留在本地,两者是一对,缺一不可。很多人推送失败,第一步就错在这里——本地压根没生成过密钥,或者生成了但从来没把公钥贴到远端账号。
Windows 下密钥默认在用户目录的.ssh文件夹里。打开 Git Bash 或者 PowerShell,执行:
cd ~/.ssh ls -al正常应该看到类似这样的输出:
id_rsa id_rsa.pub known_hostsid_rsa是私钥,id_rsa.pub是公钥。如果这个目录不存在,或者只有known_hosts没有密钥对,说明你还没生成。生成命令:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"一路回车即可,默认就会在~/.ssh/下生成id_rsa和id_rsa.pub。-C后面的邮箱只是备注,方便你在远端账号里认出这把钥匙是谁的,不参与加密。
2.2 读出 id_rsa.pub 的内容
公钥要贴到远端,得先把内容读出来。id_rsa.pub是纯文本,一行,以ssh-rsa开头,以你的邮箱备注结尾。查看方式:
cat ~/.ssh/id_rsa.pubWindows 上如果你习惯用记事本,路径一般是C:\Users\你的用户名\.ssh\id_rsa.pub,直接右键用记事本打开也行。复制的时候整行复制,从ssh-rsa一直到最后的邮箱,中间不要断行、不要多空格。我见过最常见的错误就是复制时漏了开头几个字符,或者把换行也带进去了,远端校验直接失败。
2.3 贴公钥时最容易踩的坑
远端平台(GitHub、Gitee、GitLab 等)添加公钥的入口通常在「设置 → SSH 公钥」里。这里有个高频坑:有些平台区分「个人公钥」和「仓库部署公钥」。部署公钥(Deploy Key)默认只有读权限,你拿它去 push 就会报权限不足。如果你要推送,必须用账号级别的「个人公钥」,而不是仓库里的部署公钥。
另一个坑是旧密钥没删干净。如果你之前配过一把钥匙,后来又生成了一把新的,两把都留在远端,SSH 客户端可能会拿错那把去认证。稳妥做法是:远端把旧的删掉,只留当前这把;本地如果有多把密钥,用~/.ssh/config明确指定用哪把。
# ~/.ssh/config 示例 Host gitee.com HostName gitee.com User git IdentityFile ~/.ssh/id_rsa IdentitiesOnly yesIdentitiesOnly yes这行的作用是:只使用IdentityFile指定的这把钥匙,不去挨个试其他密钥。多密钥环境下不加这行,很容易出现「明明配了却认证失败」的玄学问题。
2.4 权限问题:私钥文件不能太开放
macOS 和 Linux 下,SSH 对私钥文件权限很敏感。如果id_rsa的权限是 644(其他用户可读),SSH 会直接拒绝使用并报Permissions 0644 for 'id_rsa' are too open。修复:
chmod 600 ~/.ssh/id_rsa chmod 644 ~/.ssh/id_rsa.pubWindows 下一般不受这个限制,但如果你在 WSL 里操作,同样要注意。这一层排查完,密钥本身没问题了,就可以进入认证层验证。
3. 认证层验证:ssh -T 与 remote 地址核对
3.1 用 ssh -T 判断认证是否打通
密钥配好了不代表认证就通。最直接的验证命令是ssh -T,它会尝试用你的密钥登录远端,成功会返回一句欢迎语,失败会明确告诉你卡在哪:
ssh -T git@gitee.com成功时输出类似:
Hi username! You've successfully authenticated, but GITEE.COM does not provide shell access.看到successfully authenticated就说明密钥层和认证层都通了,问题不在钥匙上。如果返回Permission denied (publickey),说明远端不认你这把钥匙,回到第 2 节检查公钥是否贴对、是否贴的是个人公钥。
GitHub 对应命令是ssh -T git@github.com,GitLab 是ssh -T git@gitlab.com。注意用户名统一是git,不是你的账号名,这是很多人写错的地方。
3.2 核对 remote repository 地址
认证通了,下一步看 remote 地址。执行:
git remote -v输出会列出 fetch 和 push 两个地址。这里的关键是协议要匹配:如果你配的是 SSH 密钥,remote 地址就必须是git@开头的 SSH 格式;如果地址是https://开头,那走的是账号密码/Token 认证,跟你配的 SSH 密钥一点关系都没有。
# SSH 格式(配合密钥使用) origin git@gitee.com:username/repo.git (fetch) origin git@gitee.com:username/repo.git (push) # HTTPS 格式(走 Token 认证,不用密钥) origin https://gitee.com/username/repo.git (fetch) origin https://gitee.com/username/repo.git (push)如果你密钥配好了但地址是 HTTPS,那ssh -T通也没用,push 时照样让你输密码。改地址:
git remote set-url origin git@gitee.com:username/repo.git改完再git remote -v确认一遍。地址里的username/repo.git要和远端仓库实际路径完全一致,大小写敏感,写错了会报Repository not found。
3.3 一个可复制的配置片段
如果你在用一个支持自定义模型端点的编码工具(比如 Cline、Continue 这类),想把 Git 操作和模型调用分开管理,可以把远端配置和模型配置都写进项目级的 settings。下面是一个 JSON 片段示例,路径放在项目根目录的.vscode/settings.json:
{ "git.remote": "git@gitee.com:username/repo.git", "taotoken.baseUrl": "https://taotoken.net/api", "taotoken.apiKey": "sk-你的密钥", "taotoken.model": "claude-sonnet-4-20250514" }这里 Base URL、Key、Model ID 三件套要写全,缺一个工具就调不通。Base URL 用https://taotoken.net/api,Key 在控制台的 API Keys 页面生成,Model ID 按你实际要用的模型填。这样配置的好处是:Git 推送走 SSH 密钥,模型调用走独立通道,两边互不干扰,排查问题时也能快速区分是 Git 的问题还是模型端点的问题。
4. 远端层排查:分支落后与 hook 拒绝
4.1 fetch first:远端有你没有的提交
认证和地址都没问题,push 还是失败,报错长这样:
! [rejected] master -> master (fetch first) error: failed to push some refs to 'git@gitee.com:username/repo.git' hint: Updates were rejected because the remote contains work that you do hint: not have locally.这不是权限问题,是远端分支比你本地新。典型场景:你在网页上直接改了文件、或者同事推了提交,而你本地还停在旧版本。Git 拒绝覆盖,怕你丢东西。解决办法是先拉再推:
git pull origin master --rebase git push origin master用--rebase是为了把你本地的提交挪到远端最新提交之后,历史更干净。如果你不介意多一个合并提交,直接git pull origin master也行。拉的时候如果提示冲突,手动解决冲突文件后再git add和git rebase --continue。
4.2 hook declined:远端钩子拦下了你的推送
另一种远端拒绝是:
remote: error: hook declined to update refs/heads/masterhook declined说明远端的服务端钩子(server-side hook)主动拒绝了这次更新。常见原因有几个:提交信息不符合规范(比如要求带 issue 号)、提交里包含了大文件、分支被保护(protected branch)不允许直接推、或者提交者邮箱没在账号里验证过。
排查方法:先看远端返回的完整信息,钩子通常会附带拒绝原因。如果是分支保护,去仓库设置里看保护规则,或者改用特性分支推送再走合并请求:
git checkout -b feature/my-change git push origin feature/my-change推特性分支一般不会被保护规则拦,然后在网页上发起合并请求即可。如果是提交信息规范问题,用git commit --amend改掉最近一条提交信息再推。
4.3 Could not read from remote repository
这个报错通常和认证失败一起出现:
fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.它是个「兜底报错」,本身不说明具体原因,可能是密钥没配、地址写错、仓库不存在、或者网络到不了远端。排查顺序还是回到前面:先ssh -T确认认证,再git remote -v确认地址,最后确认仓库路径拼写。三者都对了还报这个,检查一下本地网络能不能解析到远端域名。
5. 常见报错对照与逐条排查
把前面几层的报错汇总成一张对照表,你对着自己终端里的信息找对应行即可:
| 报错关键字 | 根因层 | 排查动作 |
|---|---|---|
Permission denied (publickey) | 密钥/认证 | ssh -T测试;检查 id_rsa.pub 是否贴为个人公钥 |
Could not read from remote repository | 认证/地址 | 先 ssh -T,再 git remote -v 核对地址 |
! [rejected] ... (fetch first) | 远端分支 | git pull --rebase后再 push |
hook declined to update refs | 远端钩子 | 看钩子返回原因;改用特性分支推送 |
Repository not found | 地址 | 核对 username/repo 拼写与大小写 |
Permissions 0644 ... too open | 本地权限 | chmod 600 ~/.ssh/id_rsa |
fatal: remote origin already exists | 配置 | git remote set-url覆盖而非 add |
5.1 401 与 local proxy failed
如果你在编码工具里调用模型端点时看到401 Unauthorized,那是 Key 的问题,不是 Git 的问题——检查 API Key 是否复制完整、是否过期。local proxy failed一般是本地代理配置或端口占用导致,检查工具的网络设置,确认 Base URL 写的是https://taotoken.net/api而不是别的地址。这两个报错和 Git 推送无关,但经常在同一台机器上同时出现,容易混淆,排查时先分清是 Git 链路还是模型链路。
5.2 OAuth 与 auth.json
有些工具走 OAuth 授权,凭证存在auth.json里。如果 OAuth 过期,会报授权失效。这类文件的位置通常在用户配置目录下,重新走一遍授权流程即可刷新。注意不要把auth.json提交到 Git 仓库里,它包含敏感凭证,应该加进.gitignore。
5.3 三件套检查清单
无论你用的是 CC Switch、Cline MCP 还是 Codex 的 auth.json,只要涉及自定义端点,永远检查这三样:Base URL 是否为https://taotoken.net/api、Key 是否有效、Model ID 是否拼写正确。三者缺一,请求就失败。Git 推送失败和模型调用失败是两条独立的链路,分开排查效率最高。
6. 把排查链路固化成习惯
整套流程走下来,其实就三句话:ssh -T 测认证,git remote -v 看地址,git pull --rebase 解冲突。我自己的习惯是,每次换机器或者新建仓库后,先跑一遍ssh -T git@gitee.com,看到successfully authenticated再动手推代码,能省掉大量「推不上去又不知道哪错了」的时间。
密钥这块,建议一台机器只维护一把钥匙,远端也只留当前这把,多密钥环境务必用~/.ssh/config的IdentitiesOnly yes锁定。公钥复制时整行复制,别手抖。远端地址优先用 SSH 格式,和密钥配套,避免 HTTPS 和 SSH 混用带来的困惑。
如果你在配模型端点时也需要一套稳定的接入方式,可以在控制台生成 Key,接入文档里有各工具的完整配置示例;想先验证模型通不通,用模型对话页面发一条测试消息最快;长期做编码和 Agent 任务的话,Coding Plan 的额度更适合持续调用。把 Git 链路和模型链路都理顺,日常开发就少一大半莫名其妙的报错。