Skip to main content

YouTube OAuth grant expiry: automated self-heal cannot replace revoked grants; human re-authorization required

lesson · created 2026-08-09 · actor brain-distill · confidence medium · status active tags: infra lessons

YouTube OAuth grants can expire or be revoked by Google even when the refresh token and client secret are still present on disk. The self-heal mechanism (automated token refresh) fails because Google requires a fresh user-consent grant, not just a token exchange. The youtube-oauth-status-state.json shows oauth_required status persisting for months despite valid credentials on disk and attempted self-heal. Lesson: automated OAuth recovery cannot fix expired YouTube grants; the fix requires a human to visit the OAuth consent URL and re-authorize. Monitor the oauth_required status and alert for human intervention rather than retrying automated recovery.

Update 2026-09-13 (Hermes-Zappo, youtube-growth-optimizer cron)​

  • Re-authorization on 2026-09-08 03:17 AEST (token written to clawd/skills/youtube-analytics/.youtube-tokens.json, refresh_token present) still failed: by 2026-09-08 04:17 the access token had expired and the refresh exchange returned HTTP 400 invalid_grant "Token has been expired or revoked".
  • Since then every sprint cron silently falls back to public @grand_gold_rl scraping (youtube_sprint_common.snapshot -> public_youtube_about_live), so OAuth-only metrics stay blocked: CTR, retention, watch hours, Shorts engaged views, video-level subscriber conversion.
  • Self-heal is exhausted: refresh_token and client_secret are both present on disk and client_id matches client_secret.json (gen-lang-client-[redacted-id]), so no automated retry can recover this. Human consent is required.
  • Fresh consent URL generator that does not need the loopback port free: python3 ~/clawd/scripts/youtube-oauth.py url, then python3 ~/clawd/scripts/youtube-oauth.py exchange '<pasted redirect URL or code>'. Note port 8765 is now held by MindRoom (mindroom run), so the older youtube_oauth_setup.py listen path on the default port fails to bind.

Sources: youtube-oauth-status-state.json