Git 凭证 helper 锁住:pull / push 全部失败

git pull / push 每次都弹密码、卡住 30 秒、或直接报 403 / 401,是因为凭证 helper 缓存了失效凭证或配置出错。清掉缓存凭证、重新认证即可。GitHub / GitLab / Bitbucket 通用。

最快的修复: 绝大多数情况是缓存里的凭证已经失效。先把它清掉,再让 Git 用新 token 重新弹出提示。macOS 上执行 git credential-osxkeychain erase(粘贴下面两行后连按两次回车),然后 git fetch,在提示处粘贴新的 Personal Access Token——这一步能在一分钟内解决最常见的情况。如果还是不行,再按下面的分类逐项排查。

常见症状:每次 git pull 卡住约 30 秒后报 remote: HTTP Basic: Access deniedfatal: Authentication failed for 'https://github.com/...';Git 在一个无法接受交互输入的终端里弹出空白的凭证提示(VS Code 内置终端、SSH 会话、CI runner);或者 macOS 的「钥匙串访问」(Keychain Access)里 github.comInternet password 条目存的是已失效的 token,每次操作都被服务端拒绝。凭证 helper 是 Git 和系统密钥库之间的桥梁,这座桥一断,所有涉及远端的操作就全部卡死,直到你把它修好。

先确认你属于哪一类

你看到的症状最可能的原因跳到
立刻报 Authentication failed,上周还能用PAT 过期或被撤销原因 1,Step 1+2
改过密码 / 换过 token 后就开始失败缓存里是失效凭证原因 2,Step 1
每条命令都重新弹密码,从不缓存credential.helper 指向了不存在的程序原因 3,Step 4
SSH 报 Permission denied (publickey) 或卡住SSH key 没加载进 agent原因 4,Step 5
403 里出现 SAML / SSO / 单点登录字样组织 SSO 未对 PAT 授权原因 5,Step 6
Windows 上 git push 一直卡住、没有任何输出Git Credential Manager 的弹窗藏在屏幕外卡死原因 6,Step 7

常见原因

按命中率从高到低排列。

1. Personal Access Token(PAT)已过期或被撤销

GitHub、GitLab、Bitbucket 都允许给 PAT 设置有效期,GitHub 的 fine-grained token 默认一年内过期。token 一旦过期或被撤销,每次操作都返回 401/403。helper 仍然忠实地把那个已经作废的 token 发出去,被服务端拒绝。

怎么判断:在 GitHub 打开 Settings > Developer settings > Personal access tokens > Fine-grained tokens(或 Tokens (classic)),如果 token 显示 “Expired” 或已经不在列表里,就是它。服务端通常返回 fatal: Authentication failed

2. 系统密钥库里缓存了错误的凭证

改过密码或轮换过 token 之后,系统密钥库(macOS Keychain、Windows Credential Manager、Linux Secret Service)仍然把旧凭证交给 Git。Git 收到 401,想重新弹提示,可如果终端无法显示提示,就会卡住或失败。

怎么判断:macOS 上打开「钥匙串访问」(Spotlight 搜索 Keychain Access),搜 github.com,看那条 Internet password 条目——里面存的是旧 token。Linux 上执行 secret-tool search server github.com

3. credential.helper 指向了不存在的程序

macOS 升级或 Homebrew 更新之后,git-credential-osxkeychaingit-credential-manager 可能被挪了位置,或者旧配置里仍然写死了一个失效的绝对路径。Git 会悄无声息地回退到每次都重新询问。

怎么判断git config --global credential.helper 会返回一个值。如果是绝对路径,直接执行那个路径或 which <helper 名>;返回 “not found” 就说明 helper 不存在或已经移动。

4. SSH key 没有加载进 SSH agent

你把 remote 从 HTTPS 换成了 SSH(git@github.com:...),但 key 没加载进 ssh-agent。每次 SSH 操作都会在一个无法交互输入的环境里索要 passphrase。

怎么判断ssh -T git@github.com 返回 Permission denied (publickey) 或卡住;ssh-add -l 返回 The agent has no identities

5. 企业 SSO 要求对 PAT 进行授权

开启了 SAML 单点登录(SSO)的 GitHub 组织,要求每个 PAT(以及 SSH key)都必须显式授权给该组织。未做 SSO 授权的 token 在访问时返回 403,错误信息里会带 SAML SSO 字样——例如 The 'Authorization' request header field is not allowed,或提示 token 必须经过组织授权。

怎么判断:错误信息里出现 “SAML” 或 “single sign-on”。在该 token 页面(Settings > Developer settings)里,组织名旁边会有一个 “Configure SSO” 按钮。

6. Git Credential Manager 在 Windows 上死锁

Git Credential Manager(GCM)在 Windows 上有时会把认证弹窗开在终端窗口后面,或开在另一个桌面会话里,导致终端一直在等输入而无限卡住。

怎么判断git push 卡住超过 30 秒、毫无输出。切换桌面或查看任务栏,会发现一个藏起来的认证窗口。

最短修复路径

Step 1:清除缓存里的失效凭证

这是收益最高的一步。执行对应平台的命令,然后在空行上按回车,让 helper 读到输入结束:

# macOS — osxkeychain helper(GitHub 官方推荐写法)
git credential-osxkeychain erase
host=github.com
protocol=https
# (在空行上按一次回车)

# 跨平台 — Git Credential Manager
git credential-manager erase <<'EOF'
protocol=https
host=github.com
EOF

# 通用 — 用当前配置的任意 helper
git credential reject <<'EOF'
protocol=https
host=github.com
EOF

# Linux — libsecret / gnome-keyring
secret-tool clear server github.com protocol https

命令没有任何输出,就说明清除成功。

Step 2:生成新 PAT 并存入 helper

进入 GitHub Settings > Developer settings > Personal access tokens > Fine-grained tokens > Generate new token。把范围限定到你需要的仓库,并授予 Repository permissions > Contents: Read and write(只读 clone 选 Read 即可)。fine-grained token 没有旧版的 repo 勾选项——那个 scope 只在 classic token 上有。然后触发一次提示:

git fetch origin
# Username: 你的 GitHub 用户名
# Password: 粘贴新的 PAT(不是账号密码)

第一次成功后 helper 就会缓存新 token,之后不再要求输入。

Step 3:清掉重复 / 冲突的钥匙串条目(macOS)

如果存在多个 github.com 条目(换过账号后很常见),Git 可能一直取到错的那个:

# 列出条目
security find-internet-password -s github.com

# 删除某个账号的条目,然后重做 Step 1+2
security delete-internet-password -s github.com -a <account-name>

Step 4:修复缺失或错误的 helper 程序

git config --global credential.helper        # 查看当前值

# 如果返回的路径不存在,重装 GCM:
brew install --cask git-credential-manager   # macOS / Linux(Homebrew cask)
# 或
winget install --id Git.GCM                  # Windows

git config --global credential.helper manager   # 重新注册 GCM

注意:GCM 在 2.0.877 版把 helper 名从 manager-core 改成了 manager,而较新的版本已经彻底移除了 manager-core 这个旧软链接——所以旧配置现在可能直接报 git: 'credential-manager-core' is not a git command。如果你的配置里还是 manager-core,改成 manager。macOS 上如果偏好系统自带的 helper,osxkeychain 也是有效值。

Step 5:把 SSH key 加载进 agent

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh -T git@github.com   # 期望输出:"Hi <username>! You've successfully authenticated..."

要让它跨会话持久生效,在 ~/.ssh/config 里加:

Host github.com
  AddKeysToAgent yes
  UseKeychain yes
  IdentityFile ~/.ssh/id_ed25519

UseKeychain yes 是 macOS 专用的,在 Linux/Windows 上去掉这一行。

Step 6:为 SSO 授权 PAT

  1. 进入 Settings > Developer settings > Personal access tokens,点击 token 名。
  2. 找到 “Configure SSO” 按钮并点击。
  3. 在每个启用了 SSO 的组织旁点 “Authorize”。
  4. 在浏览器里走完 SSO 流程,再重试 Git 操作。

如果你用的是 SSH 而不是 PAT,授权入口在 Settings > SSH and GPG keys,对应 key 旁边同样有 “Configure SSO”。

Step 7:解开卡死的 GCM(Windows / 屏幕外弹窗)

# Windows(PowerShell)
Stop-Process -Name "git-credential-manager" -Force

# macOS / Linux
pkill -f git-credential-manager

然后重跑 Git 命令。如果 GCM 在无 GUI 的服务器上仍然想开浏览器/弹窗,强制让它改用文件存储:

git config --global credential.credentialStore plaintext   # 或改用环境变量里的 PAT

Step 8:确认已修好

git fetch --dry-run origin
git push --dry-run origin main

两条都应在一两秒内完成,既不弹提示也不卡住。退出码干净(echo $? 返回 0)、且没有 Authentication failed 那一行,就说明 helper 恢复正常了。

预防建议

  • 在交互式开发机上优先用 SSH 的 ed25519 key,而不是 HTTPS + PAT——SSH key 默认不过期,也不需要配置凭证 helper。
  • 如果坚持用 PAT,在到期前约 7 天设个日历提醒,趁 token 还没失效就轮换。
  • 能用 gh auth login(GitHub CLI)就交给它管凭证:它处理 token 存储、刷新和 SSO 授权都比手动改配置更可靠。
  • CI 环境用环境变量认证(GITHUB_TOKEN、Deploy Key,或 GitHub App installation token),而不是交互式凭证 helper——helper 是给人用的,不是给 runner 用的。
  • 同一台机器上持有多个组织的凭证时,设置 credential.useHttpPath = true,让 helper 按仓库路径分别存凭证,而不是一个 host 共用一份。
  • 在团队 Brewfile 里固定 helper,保证所有人解析到同一个受管理的路径,并在入职文档里写清各操作系统的配置命令。

常见问答 (FAQ)

Q: git config credential.helper store 把密码存进文件里安全吗? A: 不安全。store helper 把凭证以明文写进 ~/.git-credentials,任何以你的身份运行的进程都能读到。请改用 osxkeychain(macOS)、Windows Credential Manager 或 GCM——它们把 token 存在系统的加密密钥库里。

Q: CI/CD 里怎么彻底避免凭证提示? A: 把 token 注入到 URL 里:git config --global url."https://x-access-token:${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"。在用完即弃的 runner 里这样做没问题;千万别在开发机上用,否则 token 会泄漏进配置文件。

Q: 同一台机器上怎么用两个 GitHub 账号? A: SSH 最干净:在 ~/.ssh/config 里配两个 Host 别名(github-workgithub-personal),各指向不同的 IdentityFile,remote 写成 git@github-work:org/repo.git。如果用 HTTPS,设置 credential.useHttpPath = true,让每个仓库路径各存一份凭证。

Q: git credential approvereject 到底做什么? A: git credential approve 把一份凭证(用户名 + token)写进配置的 helper;git credential reject 删除指定 host/protocol 的已存凭证。两者都从 stdin 读凭证描述——这就是上面那些 erase 命令要通过管道喂 host=/protocol= 的原因。

Q: 我轮换了 PAT,但 CI 里旧的任务还是失败,为什么? A: CI 的 secret 是和你本机 helper 分开存的。你得在每个 CI secret 存储里都更新(GitHub Actions secrets、GitLab CI/CD variables、Jenkins credentials),再重跑失败的流水线——本机的修复不会动到它们。

相关阅读

标签: #git #version-control #排查