Claude Code Cron Authentication Cheatsheet

Service: Claude Code CLI (OAuth authentication for WSL cron jobs) Rotation script: solanasis-scripts/rotate-claude-token.py Last updated: 2026-03-28

Related docs:

  • Setup guide: solanasis-docs/scheduled-tasks/00-SETUP-GUIDE.md
  • Auth deep-dive: solanasis-docs/playbooks/misc/claude-code-auth-handoff-2026-03-18.md
  • Auth persistence plan: _matchkeyz/_matchkeyz_code/qa/docs/_to_implement/PLAN-claude-code-auth-persistence-wsl2.md
  • Migration plan: .claude-plans/deep-plan-scheduled-tasks-wsl-migration-2026-03-27.md

Why This Matters

Three cron jobs invoke claude -p (Fireflies sync, daily briefing, weekly blog prep). Cron does NOT source ~/.bashrc, so environment variables set there don’t reach cron jobs. The CLAUDE_CODE_OAUTH_TOKEN must be set in the crontab header explicitly.


Quick Status Check

python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --check-only

Output shows token presence in all 4 locations (.bashrc, crontab, credentials file, current shell) plus a sync check between .bashrc and crontab.


Where the Token Lives

LocationPurposeFormatPermissions
~/.bashrc (~line 202)Interactive shellsexport CLAUDE_CODE_OAUTH_TOKEN="sk-ant-oat01-..."644
Crontab headerCron jobsCLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...600 (in /var/spool/cron/)
~/.claude/.credentials.jsonClaude CLI internalJSON claudeAiOauth.accessToken600
Current shell envActive sessionSet by sourcing .bashrcSession-only

The .bashrc and crontab MUST stay in sync. The rotation script handles this.


Token Rotation

When the token expires (401 errors in cron logs)

# 1. Get a fresh token via browser login
claude auth login
 
# 2. Sync the new token to .bashrc + crontab
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --from-credentials
 
# 3. Reload in current shell
source ~/.bashrc

Manual rotation (if you have a token already)

# Preview what will change
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --dry-run <new-token>
 
# Apply to both locations
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py <new-token>
source ~/.bashrc

Crontab Environment Variables

The crontab header sets environment variables for ALL cron jobs:

# Environment for Claude Code + Docker
PATH=/home/zasage/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
SHELL=/bin/bash
TZ=America/Denver
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...
USERPROFILE=/mnt/c/Users/zasya
VariableWhy It’s Needed
PATHclaude CLI lives at ~/.local/bin/claude — not on cron’s default PATH
TZSchedules use Mountain Time
CLAUDE_CODE_OAUTH_TOKENAuthenticates claude -p calls from cron
USERPROFILEPrevents Claude from spawning powershell.exe to find Windows home

Edit with crontab -e. View with crontab -l.


Affected Cron Jobs

Only the 3 Claude-invoking tasks need the OAuth token. Backup tasks use cron-wrapper.py for Infisical secrets instead — they don’t call Claude.

TaskScheduleScript
Fireflies SyncHourly :07solanasis-private/scheduled-tasks/scripts/run-fireflies-sync.py
Daily GTM Briefing6:08 AM dailysolanasis-docs/scheduled-tasks/scripts/run-daily-briefing.py
Weekly Blog PrepMonday 8:02 AMsolanasis-docs/scheduled-tasks/scripts/run-weekly-blog-prep.py

All three call subprocess.run(["claude", "-p", ...]) which inherits the cron environment — no per-script token injection needed.


Troubleshooting

Cron job fails with 401

# Check logs for the specific job
tail -30 ~/_my/_solanasis/solanasis-private/logs/cron-fireflies.log
 
# Check token status
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --check-only
 
# If expired: re-login and sync
claude auth login
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --from-credentials

Tokens out of sync (.bashrc vs crontab)

# The check-only command will flag this:
# [WARN] .bashrc and crontab tokens DIFFER
 
# Fix by rotating to the .bashrc token:
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --from-credentials

“claude: command not found” in cron

PATH is missing ~/.local/bin. Check crontab header:

crontab -l | head -5
# Must include: PATH=/home/zasage/.local/bin:...

Token works interactively but not from cron

Test in a simulated cron environment (stripped-down env):

env -i \
  PATH=/home/zasage/.local/bin:/usr/local/bin:/usr/bin:/bin \
  HOME=/home/zasage \
  CLAUDE_CODE_OAUTH_TOKEN="$(grep CLAUDE_CODE_OAUTH_TOKEN ~/.bashrc | grep -oP 'sk-ant-oat01-[A-Za-z0-9_-]+')" \
  USERPROFILE=/mnt/c/Users/zasya \
  claude -p "Say hello" --output-format text --max-turns 1

Multiple concurrent Claude sessions causing auth issues

OAuth refresh tokens are single-use. With many concurrent claude processes, one session’s refresh can invalidate others. Keep to 2-3 max simultaneous instances. See GitHub issues #24317, #29896.


Known Limitations

  • Token expiry: OAuth access tokens expire (typically 24h from issue, but some persist longer). No auto-refresh mechanism for cron — manual rotation needed.
  • Credential wipe bug: Failed OAuth refresh can silently empty ~/.claude/.credentials.json (anthropics/claude-code#29896).
  • WSL2 clock drift: After Windows sleep, WSL2’s clock can drift, causing token validation failures. Fix: sudo hwclock -s or add to .bashrc.
  • No Infisical integration yet: Token is stored in plaintext in .bashrc and crontab. Future improvement: store in Infisical /shared/ folder and inject via cron-wrapper.py.