claude-plugins/plugins/create-scheduled-task/skills/create/SKILL.md
Cal Corum e02eb28e98 refactor: rename skill dirs to verb-based names to reduce autocomplete redundancy
Plugin:skill pairs now read as noun:verb commands instead of repeating
the plugin name. Also added concise descriptions to all SKILL.md frontmatter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 13:57:19 -05:00

7.3 KiB
Raw Blame History

name description
create Create, manage, or debug headless Claude scheduled tasks on systemd timers

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)
  • Any cognitive-memory operations (recall context, store results)

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>",
  "graph": "default",
  "working_dir": "/path/to/your/project",
  "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.
graph default Cognitive-memory graph for storing results.
working_dir (your project root) 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:

Read-only triage (Gitea + memory):

mcp__gitea-mcp__list_repo_issues mcp__gitea-mcp__get_issue_by_index mcp__gitea-mcp__list_repo_labels mcp__gitea-mcp__list_repo_pull_requests mcp__cognitive-memory__memory_recall mcp__cognitive-memory__memory_search mcp__cognitive-memory__memory_store mcp__cognitive-memory__memory_episode

Code analysis (read-only):

Read(*) Glob(*) Grep(*)

Memory maintenance:

mcp__cognitive-memory__memory_recall mcp__cognitive-memory__memory_search mcp__cognitive-memory__memory_store mcp__cognitive-memory__memory_relate mcp__cognitive-memory__memory_reflect mcp__cognitive-memory__memory_episode

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": "gitea-mcp",
    "args": ["-t", "stdio", "-host", "https://your-gitea-instance.com"],
    "env": {
      "GITEA_ACCESS_TOKEN": "<your-token>"
    }
  }
}

Cognitive Memory:

{
  "cognitive-memory": {
    "command": "python3",
    "type": "stdio",
    "args": ["/path/to/cognitive-memory/mcp_server.py"],
    "env": {}
  }
}

n8n:

{
  "n8n-mcp": {
    "command": "npx",
    "type": "stdio",
    "args": ["n8n-mcp"],
    "env": {
      "MCP_MODE": "stdio",
      "N8N_API_URL": "http://your-n8n-host:5678",
      "N8N_API_KEY": "<your-n8n-api-key>"
    }
  }
}

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>/

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. Stores a summary to cognitive-memory as a workflow + episode
  7. 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.
  • Auth: Uses Claude Max subscription via OAuth, or set ANTHROPIC_API_KEY for API key auth.
  • 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/