Google Cloud OAuth 同意屏幕处于 Testing 状态时颁发的 refresh token 会在 7 天后失效,导致无人值守的自动化任务(如 YouTube 上传管道)突然中断,发布正式应用才能根治。
TL;DR — 如果你的 Google Cloud OAuth consent screen(同意屏幕)仍处于 Testing 状态,它签发的所有 refresh token(刷新令牌)在七天后就会失效。这是写在文档里的行为,不是 bug。我的无人值守 YouTube 上传器正常运行了一周,然后在某个周六早上 06:45 停止了。修复方法不是"重新授权";而是"发布应用",而发布本身有前置条件,整个过程花了一小时,而非一分钟。
你排好了任务。它跑了一周。然后停了。
这就是问题的全貌。如果你的 cron 任务、GitHub Action 或 Task Scheduler 条目通过 refresh token 访问了 Google API,那这一幕大概率也会出现在你的未来。
我的 YouTube Shorts 流水线是我这套架构中唯一无人值守运行的部分:Claude Code 写脚本,渲染器生成视频,PowerShell runner 每天 14:00 和 21:00 上传。9 月 12 日早上 06:45,refresh token 过期了。它恰好满七天。
同一天早上,在第一个预定时间槽之前,我在 runner 前面加了一个小脚本 check_youtube_token.js。它尝试刷新 token;如果失败,runner 就在日志里写 SKIP: waiting for YouTube re-auth 然后直接退出,不触发任何操作。所以这次故障是"大声"而非"沉默"的:当天三个槽被跳过,第二天两个,加起来共丢失了五次上传,每次原因都附在旁边。第二天早上的自检也暴露了同一行日志。
但机器没法点击 Google 登录页面上的"允许"。这五个时间槽就这样一直丢失,直到有人坐下来处理。
翻开 OAuth 2.0 文档,关于 refresh token 过期的那节写道:Google Cloud 项目如果将 OAuth consent screen 配置为外部用户类型,且 publishing status 为 Testing,签发的 refresh token 会在七天后过期。
再读一遍——如果你搭建集成的方式和大多数教程演示的一样。你创建项目、配置 consent screen、把自己加为 test user、跑一次本地 auth 流程、保存 token.json、然后 schedule 任务。这整串步骤下来应用会一直停留在 Testing 状态。Token 第一天能用,第六天也能用,第七天就不行了,而整个 auth 流程里没有任何提示告诉你会这样。
重新授权只能再换来七天,没有别的。如果这就是你的修复方案,那你每周都要做一次。
真正的修复是把 publishing status 从 Testing 改为 In production。在我这里,"Publish app"按钮是灰掉的,原因如下,按我遇到的顺序排列:
Google 要求三个品牌字段,才能将外部应用发布:application homepage URL(应用首页 URL)、privacy policy URL(隐私政策 URL),以及至少一个 authorized domain(授权域名)。授权域名必须是你已证明所有权的域名,而 Google 接受的证明方式是 Search Console 验证。我的域名还没验证,所以品牌表单保存不了,Publish 按钮也就无法启用。
所以实际的操作顺序是:打开 Search Console,把域名加为 property,通过往站点根目录上传一个 HTML 文件来验证域名,回到 consent screen,填好三个品牌字段,保存,点击 Publish app,然后再跑一次 auth 流程,让新的 refresh token 在 production 状态下签发。重新授权本身大约花了一分钟。剩下那些步骤花了将近一个小时,而且是在凌晨 5 点之前就开始了,因为流水线已经挂了两天。
之后,check_youtube_token.js 报告应用处于 production 状态,token 没有过期,当天下午 14:00 的时间槽正常上传了。
在以为自己安全之前要检查的一件事:token 响应里会告诉你。当应用处于 Testing 时,auth 流程返回的 JSON 里包含一个 refresh_token_expires_in 字段,从七天开始倒计时。如果你看到这个字段,倒计时已经开始。
两处改动,都不巧妙。
第一处,token 检查跑在昂贵操作之前。Runner 以前是先用 Claude Code 启动,让上传步骤——整个流程的最后一步——去发现 token 已经失效。现在检查是第一个执行的操作;如果失败了,这次运行零成本,日志里精确写明了原因。
第二处,"跳过并附带原因"被视为一种正式结果,而非需要隐藏的失败。一行写着 SKIP: waiting for YouTube re-auth 的日志,比一行写着 OK(但实际上什么都没上传)有用得多,也比长日志底部一个堆栈跟踪有用得多。每天的自检会读取这些行,把人工任务推到待办清单最顶部。
如果你在 Google API 上构建任何无人值守的东西,顺序是:验证域名、填品牌字段、发布 consent screen、跑 auth 流程、然后 schedule。反过来做的话,效果刚好维持一周。
Google 根本不应该向 Testing 应用签发 refresh token,而不是让它在第七天悄无声息地死掉。告诉我为什么我想错了——或者告诉我,你的 scheduled jobs 里还有哪个仍在靠一个 Testing token 跑着。
这篇文章的日语版同一天发布在我的 note.com 博客。带有预先 token 检查的 runner 在 Gumroad 上免费、随意定价——我是这家店的所有者;那就是一个普通的产品链接。