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

7.1 KiB
Raw Permalink Blame History

name description
create-scheduled-task 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

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)

{
  "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:

{
  "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:

{
  "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:

[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

systemctl --user daemon-reload
systemctl --user enable --now claude-scheduled@<task-name>.timer

Managing Tasks

List all scheduled tasks

systemctl --user list-timers 'claude-scheduled*'

Manual test run

~/.config/claude-scheduled/runner.sh <task-name>

Check logs

# 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

systemctl --user disable --now claude-scheduled@<task-name>.timer

Disable ALL tasks (kill switch)

touch ~/.config/claude-scheduled/disabled
# To re-enable:
rm ~/.config/claude-scheduled/disabled

Check task run history

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