All checks were successful
Reindex Knowledge Base / reindex (push) Successful in 3s
Adds title, description, type, domain, and tags frontmatter to every doc for improved KB semantic search. The description field is prepended to every search chunk, and domain/type/tags enable filtered queries. Type values: context, guide, runbook, reference, troubleshooting Domain values match directory structure (networking, docker, etc.) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.9 KiB
5.9 KiB
| title | description | type | domain | tags | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Scheduled Tasks Overview | Headless Claude Code sessions triggered by systemd timers using runner.sh template framework and custom dispatcher scripts. Covers task layout, settings reference, cost safety, and monitoring. | context | scheduled-tasks |
|
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.shunsetsCLAUDECODEto 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_usdcap — runner.sh detectserror_max_budget_usdand warns --allowedToolsrestricts what Claude can do (read-only tasks can't Edit/Write/Bash)timeout_secondskills hung sessions- Global kill switch:
touch ~/.config/claude-scheduled/disabled - Typical cost: $0.15–0.30 per Sonnet run with MCP tools