claude-configs/skills/create-scheduled-task/SKILL.md
Cal Corum b464a10964 Sync config: add save-doc skill, update agents/skills/plugins, clean sessions
- Add skills/save-doc/ skill
- Add sessions/2121928.json
- Delete cognitive-memory skill, memory-saver agent, save-memories command
- Update CLAUDE.md, pr-reviewer, issue-worker agents
- Update mcp-manager, create-scheduled-task, paper-dynasty skills
- Update plugins (blocklist, installed, known_marketplaces, marketplaces)
- Remove old session files

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

232 lines
7.1 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.

---
name: create-scheduled-task
description: Create, manage, or debug headless Claude scheduled tasks that run on systemd timers. USE WHEN user says "create a scheduled task", "add a cron job for Claude", "schedule a task", "new scheduled task", "manage scheduled tasks", or wants Claude to do something automatically on a timer.
---
# Create Scheduled Task
## When to Activate This Skill
- "Create a scheduled task for X"
- "Schedule Claude to do X every day"
- "Add a new automated task"
- "Debug a scheduled task"
- "List scheduled tasks"
- "Manage scheduled tasks"
## System Overview
Headless Claude Code sessions triggered by systemd timers. Each task has its own prompt, MCP config, settings, and timer.
```
~/.config/claude-scheduled/
├── runner.sh # Universal task runner (DO NOT MODIFY)
├── disabled # Touch this file to globally disable all tasks
├── logs -> ~/.local/share/... # Symlink to log directory
└── tasks/
└── <task-name>/
├── prompt.md # What Claude should do
├── settings.json # Model, budget, tools, working dir
└── mcp.json # MCP servers this task needs (optional)
~/.config/systemd/user/
├── claude-scheduled@.service # Template unit (DO NOT MODIFY)
└── claude-scheduled@<task-name>.timer # One per task
```
## Creating a New Task
### Step 1: Create the task directory
```bash
mkdir -p ~/.config/claude-scheduled/tasks/<task-name>
mkdir -p ~/.local/share/claude-scheduled/logs/<task-name>
```
### Step 2: Write the prompt (`prompt.md`)
Write a clear, structured prompt that tells Claude exactly what to do. Include:
- Specific instructions (repos to check, files to read, etc.)
- Desired output format (structured text or JSON)
**Guidelines:**
- Be explicit — headless Claude has no user to ask for clarification
- Specify output format so results are parseable in logs
- Keep prompts focused on a single concern
### Step 3: Write settings (`settings.json`)
```json
{
"model": "sonnet",
"effort": "medium",
"max_budget_usd": 0.75,
"allowed_tools": "<space-separated tool list>",
"working_dir": "/mnt/NV2/Development/claude-home",
"timeout_seconds": 300
}
```
**Settings reference:**
| Field | Default | Description |
|-------|---------|-------------|
| `model` | `sonnet` | Model alias or full ID. Use `sonnet` for cost efficiency. |
| `effort` | `medium` | `low`, `medium`, or `high`. Controls reasoning depth. |
| `max_budget_usd` | `0.25` | Per-session cost ceiling. Typical triage run: ~$0.20. |
| `allowed_tools` | `Read(*) Glob(*) Grep(*)` | Space-separated tool allowlist. Principle of least privilege. |
| `working_dir` | `/mnt/NV2/Development/claude-home` | `cd` here before running. Loads that project's CLAUDE.md. |
| `timeout_seconds` | `300` | Hard timeout. 300s (5 min) is usually sufficient. |
**Common tool allowlists by task type:**
Code analysis (read-only):
```
Read(*) Glob(*) Grep(*)
```
### Step 4: Write MCP config (`mcp.json`) — if needed
Only include MCP servers the task actually needs. Use `--strict-mcp-config` (runner does this automatically when mcp.json exists).
**Available MCP server configs to copy from:**
Gitea:
```json
{
"gitea-mcp": {
"type": "stdio",
"command": "/home/cal/.local/bin/gitea-mcp",
"args": ["-t", "stdio", "-host", "https://git.manticorum.com"],
"env": {
"GITEA_ACCESS_TOKEN": "<token from ~/.claude.json>"
}
}
}
```
n8n:
```json
{
"n8n-mcp": {
"command": "npx",
"type": "stdio",
"args": ["n8n-mcp"],
"env": {
"MCP_MODE": "stdio",
"N8N_API_URL": "http://10.10.0.210:5678",
"N8N_API_KEY": "<token from ~/.claude/.mcp-full.json>"
}
}
}
```
Wrap in `{"mcpServers": { ... }}` structure.
### Step 5: Create the systemd timer
Create `~/.config/systemd/user/claude-scheduled@<task-name>.timer`:
```ini
[Unit]
Description=Claude Scheduled Task Timer: <task-name>
[Timer]
OnCalendar=<schedule>
Persistent=true
[Install]
WantedBy=timers.target
```
**Common OnCalendar expressions:**
| Schedule | Expression |
|----------|------------|
| Daily at 9am | `*-*-* 09:00:00` |
| Every 6 hours | `*-*-* 00/6:00:00` |
| Weekdays at 8am | `Mon..Fri *-*-* 08:00:00` |
| Weekly Sunday 3am | `Sun *-*-* 03:00:00` |
| Monthly 1st at midnight | `*-*-01 00:00:00` |
`Persistent=true` means if the machine was off during a scheduled run, it catches up on next boot.
### Step 6: Enable the timer
```bash
systemctl --user daemon-reload
systemctl --user enable --now claude-scheduled@<task-name>.timer
```
## Managing Tasks
### List all scheduled tasks
```bash
systemctl --user list-timers 'claude-scheduled*'
```
### Manual test run
```bash
~/.config/claude-scheduled/runner.sh <task-name>
```
### Check logs
```bash
# Latest log
ls -t ~/.local/share/claude-scheduled/logs/<task-name>/ | head -1 | xargs -I{} cat ~/.local/share/claude-scheduled/logs/<task-name>/{}
# Via journalctl (if triggered by systemd)
journalctl --user -u claude-scheduled@<task-name>.service --since today
```
### Disable a single task
```bash
systemctl --user disable --now claude-scheduled@<task-name>.timer
```
### Disable ALL tasks (kill switch)
```bash
touch ~/.config/claude-scheduled/disabled
# To re-enable:
rm ~/.config/claude-scheduled/disabled
```
### Check task run history
```bash
ls -lt ~/.local/share/claude-scheduled/logs/<task-name>/
```
## Existing Tasks
| Task | Schedule | Budget | Description |
|------|----------|--------|-------------|
| `backlog-triage` | Daily 09:00 | $0.75 | Scan Gitea issues across repos, prioritize, suggest focus |
| `sync-config` | Daily 02:00 | $0.25 | Sync ~/.claude and ~/dotfiles to Gitea (bash pre-check, Claude only on changes) |
## How the Runner Works
`runner.sh` is the universal executor. For each task it:
1. Reads `settings.json` for model, budget, tools, working dir
2. Reads `prompt.md` as the Claude prompt
3. Invokes `claude -p` with `--strict-mcp-config`, `--allowedTools`, `--no-session-persistence`, `--output-format json`
4. Unsets `CLAUDECODE` env var to allow nested sessions
5. Logs full output to `~/.local/share/claude-scheduled/logs/<task>/`
6. Rotates logs (keeps last 30 per task)
**The runner does NOT need modification to add new tasks** — just add files under `tasks/` and a timer.
## Key Constraints
- **Read-only by default**: Tasks should use `--allowedTools` to restrict to only what they need. No Bash, no Edit unless explicitly required.
- **Cost ceiling**: `max_budget_usd` is a hard limit per session. Typical Sonnet run with MCP tools: $0.150.30.
- **OAuth auth**: Uses Cal's Claude Max subscription via OAuth. No API key needed.
- **Nested sessions**: The runner unsets `CLAUDECODE` so it works from within a Claude session or from systemd.
- **Log retention**: 30 logs per task, oldest auto-deleted.
## Reference Files
- Runner: `~/.config/claude-scheduled/runner.sh`
- Template service: `~/.config/systemd/user/claude-scheduled@.service`
- Example task: `~/.config/claude-scheduled/tasks/backlog-triage/`
- Gitea issue: [claude-home#2](https://git.manticorum.com/cal/claude-home/issues/2)