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-onlyOutput 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
| Location | Purpose | Format | Permissions |
|---|---|---|---|
~/.bashrc (~line 202) | Interactive shells | export CLAUDE_CODE_OAUTH_TOKEN="sk-ant-oat01-..." | 644 |
| Crontab header | Cron jobs | CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-... | 600 (in /var/spool/cron/) |
~/.claude/.credentials.json | Claude CLI internal | JSON claudeAiOauth.accessToken | 600 |
| Current shell env | Active session | Set by sourcing .bashrc | Session-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 ~/.bashrcManual 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 ~/.bashrcCrontab 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| Variable | Why It’s Needed |
|---|---|
PATH | claude CLI lives at ~/.local/bin/claude — not on cron’s default PATH |
TZ | Schedules use Mountain Time |
CLAUDE_CODE_OAUTH_TOKEN | Authenticates claude -p calls from cron |
USERPROFILE | Prevents 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.
| Task | Schedule | Script |
|---|---|---|
| Fireflies Sync | Hourly :07 | solanasis-private/scheduled-tasks/scripts/run-fireflies-sync.py |
| Daily GTM Briefing | 6:08 AM daily | solanasis-docs/scheduled-tasks/scripts/run-daily-briefing.py |
| Weekly Blog Prep | Monday 8:02 AM | solanasis-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-credentialsTokens 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 1Multiple 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 -sor 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 viacron-wrapper.py.