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

145 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`)
```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
```bash
# 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