返回博客

Claude Code 把 git push to main 拦了三次:auto mode classifier 与 trunk-based 部署的握手陷阱

某个 Vite + GitHub Actions 的 trunk-based 部署项目,部署链路就一条:push to main → Actions 触发 → 自动构建上线。我让 Claude Code 完成功能后帮我 push,结果它在同一个 session 里连续撞墙三次——第二次甚至是绕路推 feature branch 也被拦了。

这不是权限配置问题,是 Claude Code auto mode classifier 的行为逻辑与 trunk-based 部署天然冲突,不了解这个机制很容易陷入"换姿势 → 再被拦 → 再换 → 还是被拦"的循环。

现象

Permission for this action was denied by the Claude Code auto mode classifier.
Reason: Direct push to default branch bypassing PR review.

三次被拦的具体经过:

  1. 第 1 次:直接 git push origin main → 拒绝(reason: Direct push to default branch)
  2. 第 2 次:绕路改成 git push origin main:refs/heads/feature/some-task → 仍拒绝(classifier 识别出"把 main 的内容推到远端 default branch 的引用")
  3. 第 3 次:版本号 bump 后补 push,用户刚说了句"OTA 执行完了" → 拒绝,理由是"用户的状态更新不等于 push 授权"

根因

Claude Code 的 auto mode 有一个 classifier,默认把"push to default branch(main/master)"列为高风险动作,强制要求用户每次、当次明确授权,而不是一次授权永久生效。

三个反直觉点:

1. user goal 不等于权限委托。 用户设了 /goal "完成功能再通知我",AI 理解为任务目标,但 classifier 不把它当 push 权限。stop hook 报告"功能未到生产"也不是授权信号——任务定义和操作权限是两个独立维度。

2. 前一次授权不延伸到下一次。 用户在 T1 时间点回复"授权 push"通过了,T2 另一个 push 动作(哪怕同一个 issue)需要再次授权。这和 CLAUDE.md 里的原则一致:A user approving an action once does NOT mean that they approve it in all contexts

3. 绕路推 feature branch 一样会被拦。 Classifier 的识别粒度不只是命令字面量,而是"这次 push 是否会影响 default branch 的引用"。main:refs/heads/feature/xxx 本质上是把 main 的内容推过去,和直推等价,同样触发拦截。真正安全的绕路是 feature/xxx:feature/xxx(从 feature 分支推到同名远端 feature 分支),但这意味着要提前 checkout 出来改,不是 trunk-based 的正常流程。

坑:绕路推 feature ref 也会被拦

git push origin main:refs/heads/feature/xxx 并不能绕过 classifier——它看的是"源是不是 default branch 的内容",不是命令格式。同理,gh api 等等价 API 调用也被识别为绕路意图,同样会拦。

正解

A. 用户在 prompt 框用 ! 前缀自己跑(推荐)

! git push origin main

! 前缀在 session 内执行 shell 命令,输出直接回流对话。用户自己跑 = 用户授权 + 实际 push 一步完成,绕过 classifier 拦截,也符合"用户控制高风险动作"的设计意图。

B. AI 停手,明确请求单次授权

AI 应答模板:

auto mode 拦截 push to main。请回我「授权 push」我重试,或直接在 prompt 框 ! git push origin main 自己跑。

收到"授权 push"后重试,classifier 通常会通过。

C. PR 路径 fallback(仅当项目实际需要 PR review)

git push origin HEAD:refs/heads/feature/<slug>   # 从当前 HEAD 推,不是从 main 推
gh pr create --title "..." --base main

注意:C 多出 review window + merge + 远端分支清理成本,除非项目真的要求 PR review,否则 A 是最短路径。

正解:每次 push 前主动告知用户,让他用 ! 自跑

在 trunk-based 项目的 deploy 阶段开头,主动写明"将请求 push 授权";如果流程中有多次 push(如 OTA bump 后补 push),提前告知"会请求第二次授权"——预期管理比事后解释省时间。

Tip:验证部署是否真的触发

push 成功后立刻跑:

gh run list --limit 1 --branch main

应看到 in_progress 的 Actions run,表示 CI 链路已触发。不要只看 push 成功就当部署成功。

一句话外卖

Trunk-based 部署里,git push to main 对 Claude Code 来说是每次都需要当次明确授权的高危动作——goal、前次授权、绕路 ref 都不能替代;让用户用 ! git push origin main 自跑是摩擦最小的合规路径。