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

#TaskScheduleRunnerScript/Prompt
1Fireflies Sync — hourly pull of meeting transcripts & voice notes from Fireflies AIEvery hour at :07WSL cronsolanasis-private/scheduled-tasks/scripts/run-fireflies-sync.py
2Daily GTM Briefing — morning briefing with learning, news, action itemsDaily at 6:08 AM MTWSL cronscheduled-tasks/scripts/run-daily-briefing.py
3Weekly Blog Publish Prep — blog review, social media drafts, pipeline healthMondays at 8:02 AM MTWSL cronscheduled-tasks/scripts/run-weekly-blog-prep.py
4Infisical Backup — encrypted database + .env backup to GitHubDaily at 2:00 AMWSL cronscheduled-tasks/scripts/backup-infisical.py (via cron-wrapper)
5Baserow Backup — encrypted pg_dump + media to Cloudflare R2Daily at 2:30 AMWSL cronscheduled-tasks/scripts/backup-baserow.py (via cron-wrapper)
6Baserow Backup Verify — download, decrypt, and validate backup integritySundays at 6:00 AMWSL cronscheduled-tasks/scripts/backup-baserow.py --verify (via cron-wrapper)
7Weekly Coaching Digest — coaching summary, EPUB conversion, Kindle deliverySundays at 6:00 PM MTWindows Task Schedulerscheduled-tasks/scripts/run-weekly-coaching.py
8Supabase Backup — encrypted Supabase DB backupDaily at 2:30 AMWSL cronsolanasis-scripts/supabase/run_backup_cron.py
9Infisical Backup Verify — decrypt and validate latest backupSundays at 6:30 AMWSL cronscheduled-tasks/scripts/backup-infisical.py --verify (via cron-wrapper)
10Git Sync (All Repos) — commit + push all repos under _my to GitHubDaily at 12:30 PM + 11:00 PM MTWSL cronsolanasis-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-private because meeting transcripts contain PII. Backup scripts use cron-wrapper.py to 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 status

If 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.md Section 2-3
  • Headless fix: Python wrapper scripts set GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file automatically
  • Also install: winget install Google.CloudSDK (needed by gws 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-docs

4. Create Logs Directory

mkdir C:\Users\[USERNAME]\solanasis-docs\logs

Setup: 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\:

ScriptTaskWorking DirKey Features
run-fireflies-sync.pyHourly Fireflies syncsolanasis-docs10-min timeout, $2 budget, 30-day log rotation
run-daily-briefing.pyDaily GTM briefing_solanasis (parent)Same + GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file for headless gws
run-weekly-blog-prep.pyWeekly blog pipelinesolanasis-docsSame + GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file

Each script:

  • Calls claude -p with a task-specific prompt and --allowedTools
  • Logs stdout/stderr to solanasis-docs/logs/{task}-{timestamp}.log
  • Enforces --max-turns 25 and --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 claude CLI lives at ~/.local/bin/claude which is not on the default cron PATH. Without this, claude -p calls 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.exe to 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 .env files 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):

LocationPurposePermissions
~/.bashrc (line ~202)Interactive shells644 (user-readable)
Crontab headerCron jobs600 (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-credentials

After rotation, reload in the current shell: source ~/.bashrc

How to get a fresh token when expired:

  1. Run claude auth login in a WSL terminal (opens browser OAuth flow)
  2. After login completes, extract the new token:
    python3 ~/_my/_solanasis/solanasis-scripts/rotate-claude-token.py --from-credentials
  3. This updates both .bashrc and 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.md for 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 entries

Testing 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.py

Windows (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 --verify

Check 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)

StepStatus
Claude Code CLI installed (Windows)Done
Fireflies MCP configuredDone
GWS CLI installed + authenticated (Windows)Done
Task Scheduler registrationDone (register-tasks.ps1, 7 tasks)
Cowork Desktop tasks disabledDone

Phase 2: PII Separation (2026-03-27)

StepStatus
Meeting notes → solanasis-privateDone
Daily notes/briefings → solanasis-dmitri-daily-notesDone
Fireflies sync → WSL cron + solanasis-privateDone
Windows Fireflies task disabledDone

Phase 3: Full WSL Cron Migration (2026-03-28)

StepStatus
Crontab PATH fix (~/.local/bin for claude)Done
Crontab TZ set to America/DenverDone
Scripts updated for cross-platform (OS detection)Done — all scripts use platform.system() or _resolve_path()
cron-wrapper.py for secret injectionDone — 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 digestPending — still Windows Task Scheduler (auxiliary scripts need update)
GWS CLI installed in WSLPending — needed for daily briefing calendar section
Disable remaining Windows tasksPending — after 24h WSL verification

Troubleshooting

IssueFix
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 firingCheck systemctl status cron is active. Check journalctl -u cron --since "1 hour ago" for errors
WSL cron: backup missing secretsBackup 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 failsCheck 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 repoCheck 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 rebaseThe 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 branchThe 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 availableConfigure MCPs in .claude/settings.json — they don’t transfer from Cowork
Task runs but produces no outputCheck log file in logs/; likely a tool permission issue — add missing tools to --allowedTools
Fireflies returns empty resultsVerify the Fireflies API key is configured in MCP settings
gws keyring error from SSH/Task SchedulerSet GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file (Python wrappers do this automatically)
gws auth expiredRDP 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 calendarVerify gws calendar +agenda --today --timezone America/Denver works from CLI
Daily briefing missing meeting intelligenceFireflies 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 secretsSecrets 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 restartEnsure 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 403Verify CLOUDFLARE_API_TOKEN has R2 permissions. Check Cloudflare dashboard → R2 is enabled
Docker path mangling in Git BashPrefix 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-private after the docs.solanasis.com exposure incident. See the incident log at solanasis-incident-reports/incidents/2026-03-27-docs-exposure-incident-log.md and solanasis-private/CLAUDE.md for rules.