claude-home/scheduled-tasks/CONTEXT.md
Cal Corum b50808eb8e docs: add scheduled-tasks documentation and update CLAUDE.md routing (#2)
Adds scheduled-tasks/CONTEXT.md documenting the implemented headless
Claude scheduled task system: runner.sh framework, custom dispatcher
scripts, active tasks, settings reference, auth, monitoring, and
cost safety guardrails.

Updates CLAUDE.md routing table to load CONTEXT.md alongside the
SKILL.md for all scheduled-task-related topics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 03:55:02 +00:00

5.5 KiB
Raw Blame History

Scheduled Tasks — Headless Claude Sessions on a Timer

Headless Claude Code sessions triggered by systemd timers. Runs claude -p with task-specific prompts, tools, and cost limits — no TTY required.

For a guide on creating new tasks, see: ~/.claude/skills/create-scheduled-task/SKILL.md

File Layout

~/.config/claude-scheduled/
├── runner.sh                       # Universal task runner (template-based tasks)
├── disabled                        # Touch to globally pause all template tasks
├── tasks/
│   ├── backlog-triage/             # Example template task
│   │   ├── prompt.md               # What Claude should do
│   │   ├── settings.json           # Model, budget, tools, working dir
│   │   └── mcp.json                # MCP servers (optional)
│   ├── issue-poller/               # Used by issue-poller.sh (not runner.sh)
│   ├── issue-worker/               # Agent instructions + settings
│   └── pr-reviewer/                # Agent instructions + settings
├── issue-poller.sh                 # Custom dispatcher for issue scanning
├── issue-dispatcher.sh             # Dispatches AI agent per issue
├── pr-reviewer-dispatcher.sh       # Dispatches AI reviewer per open PR
└── repos.json                      # Gitea repos to scan

~/.config/systemd/user/
├── claude-scheduled@.service       # Template service (used by runner.sh tasks)
├── claude-scheduled@backlog-triage.timer
├── claude-issue-poller.service/timer   # Every 30 min
├── claude-pr-reviewer.service/timer
├── claude-issue-dispatcher.service
└── claude-daily-report.service/timer

~/.local/share/claude-scheduled/
└── logs/<task-name>/               # Timestamped .log files (last 30 kept)

Source files are in ~/dotfiles/claude-scheduled/ and symlinked into place.

Two Execution Patterns

1. runner.sh (Template Framework)

Used by tasks with only a prompt and settings — no custom logic needed.

claude-scheduled@<task>.timer
    → claude-scheduled@.service
        → runner.sh <task>
            reads tasks/<task>/{prompt.md,settings.json,mcp.json}
            invokes: claude -p <prompt> --model --effort --max-budget-usd
                          --allowedTools --output-format json
            stores result to cognitive-memory
            optionally POSTs to Discord webhook

Kill switch: touch ~/.config/claude-scheduled/disabled pauses all runner.sh tasks.

2. Custom Dispatcher Scripts

Used for tasks that need conditional dispatch, parallel agents, or complex logic.

claude-issue-poller.timer (every 30 min)
    → issue-poller.sh
        → scans repos.json for new open issues
        → calls issue-dispatcher.sh per issue
            → spawns claude -p (issue-worker agent)

claude-pr-reviewer.timer
    → pr-reviewer-dispatcher.sh
        → scans open PRs needing review
        → spawns claude -p (pr-reviewer agent) per PR

claude-daily-report.timer
    → daily-report.py
        → generates summary, posts to Discord

Active Tasks

Task Trigger Type Description
backlog-triage Weekdays 09:15 runner.sh Scan Gitea issues, prioritize, suggest focus
issue-poller Every 30 min custom Find new issues, dispatch AI workers
issue-dispatcher On-demand custom Fix a single issue, open a PR
pr-reviewer On timer custom Review open PRs, post formal reviews
daily-report Daily custom Summarize activity, post to Discord

Settings Reference (settings.json)

{
  "model": "sonnet",
  "effort": "medium",
  "max_budget_usd": 0.75,
  "allowed_tools": "Read(*) Glob(*) Grep(*)",
  "graph": "default",
  "working_dir": "/mnt/NV2/Development/claude-home",
  "timeout_seconds": 300,
  "notify_webhook": "https://discord.com/api/webhooks/..."
}
Field Default Notes
model sonnet Model alias. Use sonnet for cost efficiency.
effort medium low, medium, high — controls reasoning depth
max_budget_usd 0.25 Hard cost ceiling per session
allowed_tools Read(*) Glob(*) Grep(*) Principle of least privilege
graph default Cognitive-memory graph for output storage
working_dir claude-home cd here before running — loads that project's CLAUDE.md
timeout_seconds 300 Hard timeout via timeout(1)
notify_webhook Discord webhook URL for result posting (optional)

Auth and Environment

  • Uses Cal's Claude Max subscription via OAuth (no API key needed)
  • runner.sh unsets CLAUDECODE to allow nested sessions (running from within Claude Code)
  • MCP servers configured per-task via mcp.json + --strict-mcp-config

Monitoring

# List all active timers
systemctl --user list-timers 'claude*'

# Check logs for a task
journalctl --user -u claude-scheduled@backlog-triage.service --since today

# Latest log file for runner.sh task
ls -t ~/.local/share/claude-scheduled/logs/backlog-triage/ | head -1

# Manual test run
~/.config/claude-scheduled/runner.sh backlog-triage

Cost Safety

  • Per-task max_budget_usd cap — runner.sh detects error_max_budget_usd and warns
  • --allowedTools restricts what Claude can do (read-only tasks can't Edit/Write/Bash)
  • timeout_seconds kills hung sessions
  • Global kill switch: touch ~/.config/claude-scheduled/disabled
  • Typical cost: $0.150.30 per Sonnet run with MCP tools