Solanasis Scheduled Tasks — Server Reference Guide
Purpose: Reference guide for automated tasks running via WSL cron (primary) and Windows Task Scheduler (legacy/fallback). All scripts are cross-platform — they auto-detect Windows vs Linux paths. Tasks 1-3 are Claude Code prompt tasks, tasks 4-6 are standalone backup scripts, task 7 is the weekly coaching digest.
Created: March 23, 2026 | Last updated: April 1, 2026 Primary runner: WSL2 (Ubuntu) cron — all tasks migrated 2026-03-28 Fallback runner: Windows 11 Pro Task Scheduler (tasks disabled, kept for rollback) Workspace:
~/_my/_solanasis(WSL) /C:\_my\_solanasis(Windows)
Overview: Scheduled Tasks
| # | Task | Schedule | Runner | Script/Prompt |
|---|---|---|---|---|
| 1 | Fireflies Sync — hourly pull of meeting transcripts & voice notes from Fireflies AI | Every hour at :07 | WSL cron | solanasis-private/scheduled-tasks/scripts/run-fireflies-sync.py |
| 2 | Daily GTM Briefing — morning briefing with learning, news, action items | Daily at 6:08 AM MT | WSL cron | scheduled-tasks/scripts/run-daily-briefing.py |
| 3 | Weekly Blog Publish Prep — blog review, social media drafts, pipeline health | Mondays at 8:02 AM MT | WSL cron | scheduled-tasks/scripts/run-weekly-blog-prep.py |
| 4 | Infisical Backup — encrypted database + .env backup to GitHub | Daily at 2:00 AM | WSL cron | scheduled-tasks/scripts/backup-infisical.py (via cron-wrapper) |
| 5 | Baserow Backup — encrypted pg_dump + media to Cloudflare R2 | Daily at 2:30 AM | WSL cron | scheduled-tasks/scripts/backup-baserow.py (via cron-wrapper) |
| 6 | Baserow Backup Verify — download, decrypt, and validate backup integrity | Sundays at 6:00 AM | WSL cron | scheduled-tasks/scripts/backup-baserow.py --verify (via cron-wrapper) |
| 7 | Weekly Coaching Digest — coaching summary, EPUB conversion, Kindle delivery | Sundays at 6:00 PM MT | Windows Task Scheduler | scheduled-tasks/scripts/run-weekly-coaching.py |
| 8 | Supabase Backup — encrypted Supabase DB backup | Daily at 2:30 AM | WSL cron | solanasis-scripts/supabase/run_backup_cron.py |
| 9 | Infisical Backup Verify — decrypt and validate latest backup | Sundays at 6:30 AM | WSL cron | scheduled-tasks/scripts/backup-infisical.py --verify (via cron-wrapper) |
| 10 | Git Sync (All Repos) — commit + push all repos under _my to GitHub | Daily at 12:30 PM + 11:00 PM MT | WSL cron | solanasis-scripts/sync_all_repos.py |
WSL Migration (2026-03-28): All tasks except Weekly Coaching Digest now run via WSL cron. Windows Task Scheduler tasks should be disabled (not deleted) as a rollback option. The Fireflies script lives in
solanasis-privatebecause meeting transcripts contain PII. Backup scripts usecron-wrapper.pyto inject secrets from Infisical (no .env files needed in WSL).
Prerequisites
1. Claude Code CLI Installed on Windows Server
# Verify Claude Code is installed and authenticated
claude --version
claude auth statusIf not installed, follow: https://docs.claude.com/en/docs/claude-code/getting-started
2. Required MCP Connectors
These MCPs must be configured in the server’s Claude Code settings (.claude/settings.json or global config):
- Fireflies AI — for meeting transcript sync (used by Task 1 and Task 2)
- Web Search — for news in daily briefing and blog trend validation (used by Tasks 2 and 3)
Note: Google Calendar is accessed via the gws CLI through the Bash tool — no MCP connector needed. See playbooks/gws-cli-google-workspace-playbook.md.
2b. GWS CLI (Google Workspace CLI)
Required for the daily briefing to cross-reference calendar events.
- Install:
npm install -g @googleworkspace/cli(requires Node.js 18+) - Auth: Requires RDP (one-time) — see
playbooks/gws-cli-google-workspace-playbook.mdSection 2-3 - Headless fix: Python wrapper scripts set
GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=fileautomatically - Also install:
winget install Google.CloudSDK(needed bygws auth setup) - Verify:
gws calendar +agenda --today --timezone America/Denver
3. Solanasis-Docs Folder on Server
The solanasis-docs folder must be accessible on the server. Options:
- Option A (Recommended): Git clone the repo to the server and set up a sync (pull before each task runs)
- Option B: Use a cloud sync (Google Drive, OneDrive) to keep the folder in sync
- Option C: Map a network drive to wherever the folder lives
# Example: Clone to server
cd C:\Users\[USERNAME]
git clone [your-repo-url] solanasis-docs4. Create Logs Directory
mkdir C:\Users\[USERNAME]\solanasis-docs\logsSetup: Windows Task Scheduler
Method A: Python Wrappers + Task Scheduler (Active — What’s Deployed)
Python wrapper scripts handle logging, error capture, timeout safety, budget caps, and log rotation. They call claude -p with the prompt and allowed tools.
Wrapper Scripts (already deployed)
Located at C:\_my\_solanasis\solanasis-docs\scheduled-tasks\scripts\:
| Script | Task | Working Dir | Key Features |
|---|---|---|---|
run-fireflies-sync.py | Hourly Fireflies sync | solanasis-docs | 10-min timeout, $2 budget, 30-day log rotation |
run-daily-briefing.py | Daily GTM briefing | _solanasis (parent) | Same + GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file for headless gws |
run-weekly-blog-prep.py | Weekly blog pipeline | solanasis-docs | Same + GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file |
Each script:
- Calls
claude -pwith a task-specific prompt and--allowedTools - Logs stdout/stderr to
solanasis-docs/logs/{task}-{timestamp}.log - Enforces
--max-turns 25and--max-budget-usd 2.00 - Cleans up log files older than 30 days
Task Registration (already deployed)
Registration script at scripts/register-tasks.ps1 was run to create all 6 tasks.
Tasks live under \Solanasis\ in Task Scheduler (not at the root level).
To re-register or update tasks, run as Administrator:
powershell -ExecutionPolicy Bypass -File "C:\_my\_solanasis\solanasis-docs\scheduled-tasks\scripts\register-tasks.ps1"To verify:
Get-ScheduledTask -TaskPath "\Solanasis\" | Format-Table TaskName, State -AutoSize
# Expected: 6 tasks, all "Ready"To force-run a task manually:
schtasks /Run /TN "\Solanasis\Solanasis - Fireflies Sync"
schtasks /Run /TN "\Solanasis\Solanasis - Daily GTM Briefing"
schtasks /Run /TN "\Solanasis\Solanasis - Weekly Blog Prep"
schtasks /Run /TN "\Solanasis\Solanasis - Infisical Backup"
schtasks /Run /TN "\Solanasis\Solanasis - Baserow Backup"
schtasks /Run /TN "\Solanasis\Solanasis - Baserow Backup Verify"Setup: WSL Cron (Active — What’s Deployed)
All tasks except Weekly Coaching now run via cron on WSL2 (Ubuntu). The crontab uses environment variables for PATH and timezone at the top.
Crontab (view with crontab -l)
# 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 — also set in ~/.bashrc:202
# To rotate: python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py <new-token>
CLAUDE_CODE_OAUTH_TOKEN=<token-value>
USERPROFILE=/mnt/c/Users/zasya
# Fireflies sync — hourly at :07
7 * * * * python3 .../solanasis-private/scheduled-tasks/scripts/run-fireflies-sync.py >> .../logs/cron-fireflies.log 2>&1
# Daily GTM Briefing — 6:08 AM daily
8 6 * * * python3 .../solanasis-docs/scheduled-tasks/scripts/run-daily-briefing.py >> .../logs/cron-briefing.log 2>&1
# Infisical backup — daily 2:00 AM (secrets via cron-wrapper)
0 2 * * * python3 .../cron-wrapper.py shared -- python3 .../backup-infisical.py >> .../logs/cron-infisical-backup.log 2>&1
# Baserow backup — daily 2:30 AM (secrets via cron-wrapper)
30 2 * * * python3 .../cron-wrapper.py shared -- python3 .../backup-baserow.py >> .../logs/cron-baserow-backup.log 2>&1
# Supabase backup — daily 2:30 AM
30 2 * * * python3 .../supabase/run_backup_cron.py >> .../logs/supabase-cron.log 2>&1
# Infisical backup verify — Sunday 6:30 AM
30 6 * * 0 python3 .../cron-wrapper.py shared -- python3 .../backup-infisical.py --verify >> .../logs/cron-infisical-verify.log 2>&1
# Baserow backup verify — Sunday 6:00 AM
0 6 * * 0 python3 .../cron-wrapper.py shared -- python3 .../backup-baserow.py --verify >> .../logs/cron-baserow-verify.log 2>&1
# Weekly blog prep — Monday 8:02 AM
2 8 * * 1 python3 .../solanasis-docs/scheduled-tasks/scripts/run-weekly-blog-prep.py >> .../logs/cron-blog-prep.log 2>&1
# Nightly security scan — daily 3:00 AM
0 3 * * * python3 .../solanasis-scripts/security/nightly_security_scan.py >> .../security-scans/cron-nightly.log 2>&1
# Midday git sync — commit + push all repos at 12:30 PM
30 12 * * * python3 .../solanasis-scripts/sync_all_repos.py --root ~/_my --log-file .../logs/cron-git-sync.log >> .../logs/cron-git-sync.log 2>&1
# Nightly git sync — commit + push all repos at 11:00 PM
0 23 * * * python3 .../solanasis-scripts/sync_all_repos.py --root ~/_my --log-file .../logs/cron-git-sync.log >> .../logs/cron-git-sync.log 2>&1(Paths abbreviated with ... for readability — full paths use /home/zasage/_my/_solanasis/)
Key Design Decisions
- PATH at top of crontab: Required because
claudeCLI lives at~/.local/bin/claudewhich is not on the default cron PATH. Without this,claude -pcalls fail. - TZ=America/Denver: All schedules use Mountain Time.
- CLAUDE_CODE_OAUTH_TOKEN at top of crontab: Required for Claude-invoking tasks (1-3).
Cron doesn’t source
.bashrc, so the token must be set explicitly. See section below. - USERPROFILE at top of crontab: Prevents Claude from spawning
powershell.exeto discover the Windows home directory. Set to/mnt/c/Users/zasya. - cron-wrapper.py: Injects secrets from Infisical before running backup scripts.
This replaces the need for
.envfiles on WSL. Usage:cron-wrapper.py <folder> -- <command>. - Cross-platform scripts: All scripts use
platform.system()or_resolve_path()to auto-detect Windows vs Linux paths, so they work on both platforms.
Claude Code OAuth Token for Cron
Claude-invoking cron jobs (Fireflies sync, daily briefing, weekly blog prep) need
CLAUDE_CODE_OAUTH_TOKEN in their environment. Cron doesn’t source .bashrc, so
the token is set in the crontab header alongside PATH and TZ.
Where the token lives (2 places — must stay in sync):
| Location | Purpose | Permissions |
|---|---|---|
~/.bashrc (line ~202) | Interactive shells | 644 (user-readable) |
| Crontab header | Cron jobs | 600 (owner-only, in /var/spool/cron/) |
Token rotation script:
# Check current status — shows token in each location + sync check
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --check-only
# Rotate to a new token (updates .bashrc + crontab)
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py <new-token>
# Dry-run first to see what would change
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --dry-run <new-token>
# Extract token from ~/.claude/.credentials.json after a fresh login
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --from-credentialsAfter rotation, reload in the current shell: source ~/.bashrc
How to get a fresh token when expired:
- Run
claude auth loginin a WSL terminal (opens browser OAuth flow) - After login completes, extract the new token:
python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --from-credentials - This updates both
.bashrcand crontab automatically
Known issues with token persistence:
- OAuth access tokens can expire (check with
--check-only) - Multiple concurrent Claude sessions can cause refresh token race conditions
- WSL2 VM sleep/wake can cause clock drift → token validation failures
- See
playbooks/misc/claude-code-auth-handoff-2026-03-18.mdfor deep-dive
Cron Service
Cron must be running. Verify with:
systemctl status cron
# Should show "active (running)"If not running: sudo systemctl enable --now cron
Edit Crontab
crontab -e # Opens in default editor
crontab -l # View current entriesTesting Each Task
WSL (Primary)
# Test Fireflies Sync
python3 ~/…/solanasis-private/scheduled-tasks/scripts/run-fireflies-sync.py
# Test Daily Briefing
python3 ~/…/solanasis-docs/scheduled-tasks/scripts/run-daily-briefing.py
# Test Weekly Blog Prep
python3 ~/…/solanasis-docs/scheduled-tasks/scripts/run-weekly-blog-prep.py
# Test Infisical Backup (with secrets injection)
python3 ~/…/solanasis-docs/scheduled-tasks/scripts/cron-wrapper.py shared -- python3 ~/…/solanasis-docs/scheduled-tasks/scripts/backup-infisical.py
# Test Baserow Backup (with secrets injection)
python3 ~/…/solanasis-docs/scheduled-tasks/scripts/cron-wrapper.py shared -- python3 ~/…/solanasis-docs/scheduled-tasks/scripts/backup-baserow.py
# Test Baserow Backup Verify
python3 ~/…/solanasis-docs/scheduled-tasks/scripts/cron-wrapper.py shared -- python3 ~/…/solanasis-docs/scheduled-tasks/scripts/backup-baserow.py --verify
# Test Git Sync — dry-run (safe, changes nothing)
python3 ~/…/solanasis-scripts/sync_all_repos.py --dry-run
# Test Git Sync — status only (shows clean/dirty repos)
python3 ~/…/solanasis-scripts/sync_all_repos.py --status
# Test Git Sync — real run (commits + pushes all repos)
python3 ~/…/solanasis-scripts/sync_all_repos.pyWindows (Fallback)
python C:\_my\_solanasis\solanasis-docs\scheduled-tasks\scripts\run-daily-briefing.py
python C:\_my\_solanasis\solanasis-docs\scheduled-tasks\scripts\run-weekly-blog-prep.py
python C:\_my\_solanasis\solanasis-docs\scheduled-tasks\scripts\backup-infisical.py
python C:\_my\_solanasis\solanasis-docs\scheduled-tasks\scripts\backup-baserow.py
python C:\_my\_solanasis\solanasis-docs\scheduled-tasks\scripts\backup-baserow.py --verifyCheck logs at solanasis-docs/logs/ (WSL) or solanasis-private/logs/ (Fireflies) for exit code 0.
Migration Status
Phase 1: Cowork Desktop → Windows Task Scheduler (2026-03-23)
| Step | Status |
|---|---|
| Claude Code CLI installed (Windows) | Done |
| Fireflies MCP configured | Done |
| GWS CLI installed + authenticated (Windows) | Done |
| Task Scheduler registration | Done (register-tasks.ps1, 7 tasks) |
| Cowork Desktop tasks disabled | Done |
Phase 2: PII Separation (2026-03-27)
| Step | Status |
|---|---|
| Meeting notes → solanasis-private | Done |
| Daily notes/briefings → solanasis-dmitri-daily-notes | Done |
| Fireflies sync → WSL cron + solanasis-private | Done |
| Windows Fireflies task disabled | Done |
Phase 3: Full WSL Cron Migration (2026-03-28)
| Step | Status |
|---|---|
Crontab PATH fix (~/.local/bin for claude) | Done |
| Crontab TZ set to America/Denver | Done |
| Scripts updated for cross-platform (OS detection) | Done — all scripts use platform.system() or _resolve_path() |
cron-wrapper.py for secret injection | Done — used by backup scripts |
| Fireflies sync (hourly :07) | Done — WSL cron, verified 2026-03-28 |
| Daily briefing (6:08 AM) | Done — WSL cron, output → solanasis-dmitri-daily-notes/briefings/ |
| Weekly blog prep (Mon 8:02 AM) | Done — WSL cron |
| Infisical backup (2:00 AM) | Done — WSL cron via cron-wrapper, verified |
| Infisical verify (Sun 6:30 AM) | Done — WSL cron via cron-wrapper, verified |
| Baserow backup (2:30 AM) | Done — WSL cron via cron-wrapper |
| Baserow verify (Sun 6:00 AM) | Done — WSL cron via cron-wrapper |
| Supabase backup (2:30 AM) | Done — WSL cron |
| Git sync — midday (12:30 PM) + nightly (11:00 PM) | Done — WSL cron, added 2026-04-01 |
| Weekly coaching digest | Pending — still Windows Task Scheduler (auxiliary scripts need update) |
| GWS CLI installed in WSL | Pending — needed for daily briefing calendar section |
| Disable remaining Windows tasks | Pending — after 24h WSL verification |
Troubleshooting
| Issue | Fix |
|---|---|
| WSL cron: “claude: command not found” | Ensure PATH=/home/zasage/.local/bin:... is set at top of crontab (crontab -e) |
| WSL cron: OAuth token expired (401) | 1) Run claude auth login in WSL terminal. 2) Run python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --from-credentials to sync new token to .bashrc + crontab. 3) Verify: python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --check-only |
| WSL cron: task not firing | Check systemctl status cron is active. Check journalctl -u cron --since "1 hour ago" for errors |
| WSL cron: backup missing secrets | Backup scripts use cron-wrapper.py shared -- ... to inject secrets from Infisical. Verify manage_secrets.py works: python3 ~/…/infisical/manage_secrets.py list -f shared |
| WSL cron: Infisical git push fails | Check cd infisical-backups && git status && git log --oneline -3. If diverged from remote, run git pull --rebase && git push |
| Git sync: push fails for a repo | Check the log at solanasis-docs/logs/cron-git-sync.log. Common causes: SSH key not loaded, remote branch protection rules, or diverged history. Fix the repo manually, then the next sync run will retry |
| Git sync: repo stuck in rebase | The script auto-detects and aborts stale rebases on each run. If it still fails, manually run cd <repo> && git rebase --abort |
| Git sync: repo on wrong branch | The script syncs whatever branch is checked out. If a repo is on a feature branch, it will push to that branch. Switch branches manually if needed before the next sync |
| ”claude: command not found” (Windows) | Add Claude Code to system PATH |
| MCP tools not available | Configure MCPs in .claude/settings.json — they don’t transfer from Cowork |
| Task runs but produces no output | Check log file in logs/; likely a tool permission issue — add missing tools to --allowedTools |
| Fireflies returns empty results | Verify the Fireflies API key is configured in MCP settings |
gws keyring error from SSH/Task Scheduler | Set GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file (Python wrappers do this automatically) |
gws auth expired | RDP into server → gws auth login (refresh token usually handles this automatically) |
| Task Scheduler shows “Last Run Result: 0x1” | Path issue — verify Python path and script paths in register-tasks.ps1 |
| Daily briefing missing calendar | Verify gws calendar +agenda --today --timezone America/Denver works from CLI |
| Daily briefing missing meeting intelligence | Fireflies sync must run BEFORE the daily briefing — 6:08 AM schedule assumes hourly sync has already run |
| Baserow pg_dump fails “Peer authentication failed” | Must use docker exec -u postgres (not -u baserow). The all-in-one container runs PostgreSQL as postgres OS user with peer auth |
| Baserow backup can’t find secrets | Secrets load from .env files (solanasis-docs/.env and solanasis-site/.env). Run python sync_env.py --all from the infisical directory to regenerate |
| Baserow templates came back after restart | Ensure SYNC_TEMPLATES_ON_STARTUP=false is in docker-compose.yml and recreate container with docker compose up -d (not just docker restart) |
| R2 upload fails 403 | Verify CLOUDFLARE_API_TOKEN has R2 permissions. Check Cloudflare dashboard → R2 is enabled |
| Docker path mangling in Git Bash | Prefix commands with MSYS_NO_PATHCONV=1 to prevent Unix paths being rewritten to Windows paths |
File Structure
_solanasis/
├── solanasis-dmitri-daily-notes/ ← Daily notes + briefings (separate repo)
│ ├── briefings/ ← Output: daily GTM briefing files
│ ├── daily-notes/ ← Dmitri's personal daily notes
│ └── logs/ ← Cron logs for daily briefing
├── solanasis-private/ ← PII-bearing meeting notes (separate repo)
│ ├── meeting-notes/
│ │ ├── meeting-transcripts/ ← Output: Fireflies meeting files
│ │ ├── voice-notes/ ← Output: Fireflies voice note files
│ │ └── fireflies-sync-status.md ← Output: sync tracking
│ ├── daily-notes/ ← Daily coaching sessions, status notes
│ ├── scheduled-tasks/
│ │ ├── 01-fireflies-sync-prompt.md ← Fireflies sync instructions (moved here 2026-03-27)
│ │ └── scripts/
│ │ └── run-fireflies-sync.py ← Hourly wrapper (WSL cron, moved here 2026-03-27)
│ └── logs/ ← Fireflies sync logs (auto-rotated 30 days)
├── solanasis-scripts/
│ └── sync_all_repos.py ← Git sync: commit + push all repos (Task 10, cross-platform)
└── solanasis-docs/
├── scheduled-tasks/
│ ├── 00-SETUP-GUIDE.md ← This file
│ ├── 02-daily-gtm-briefing-prompt.md ← Daily briefing instructions
│ ├── 03-weekly-blog-publish-prep-prompt.md ← Blog prep instructions
│ ├── 04-weekly-coaching-digest-prompt.md ← Weekly coaching instructions
│ ├── MASTER-SETUP-PROMPT.md ← One-shot setup prompt (for fresh installs)
│ └── scripts/
│ ├── register-tasks.ps1 ← Windows Task Scheduler registration (legacy)
│ ├── cron-wrapper.py ← Secret injection wrapper for cron jobs
│ ├── run-daily-briefing.py ← Daily wrapper (cross-platform)
│ ├── run-weekly-blog-prep.py ← Weekly wrapper (cross-platform)
│ ├── run-weekly-coaching.py ← Weekly coaching wrapper (cross-platform)
│ ├── convert-to-epub.py ← EPUB conversion for Kindle (Windows-only)
│ ├── send-to-kindle.py ← Kindle delivery (Windows-only)
│ ├── backup-infisical.py ← Daily Infisical backup (cross-platform, → GitHub)
│ └── backup-baserow.py ← Daily Baserow backup (cross-platform, → R2)
├── logs/ ← Task execution logs (auto-rotated 30 days)
├── content-creation/ ← Output: blog prep reports + social media
└── playbooks/
└── gws-cli-google-workspace-playbook.md ← GWS CLI reference + auth docs
PII separation (2026-03-27): Meeting notes, voice notes, and daily notes were moved to
solanasis-privateafter the docs.solanasis.com exposure incident. See the incident log atsolanasis-incident-reports/incidents/2026-03-27-docs-exposure-incident-log.mdandsolanasis-private/CLAUDE.mdfor rules.