All checks were successful
Reindex Knowledge Base / reindex (push) Successful in 2s
Removed cognitive-memory MCP, timers, and symlink system references. Replaced with kb-search MCP and /save-doc skill workflow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
168 lines
6.5 KiB
Markdown
168 lines
6.5 KiB
Markdown
---
|
||
title: "Scheduled Tasks Overview"
|
||
description: "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."
|
||
type: context
|
||
domain: scheduled-tasks
|
||
tags: [claude-code, systemd, timers, automation, headless, runner, issue-poller, pr-reviewer, backlog-triage, sync-kb, sync-config]
|
||
---
|
||
|
||
# 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-scheduled@sync-config.timer # Daily 02:00
|
||
├── claude-issue-poller.service/timer # Every 30 min
|
||
├── claude-pr-reviewer.service/timer
|
||
├── claude-issue-dispatcher.service
|
||
├── claude-daily-report.service/timer
|
||
├── sync-kb.service/timer # Every 2 hours (plain bash, no Claude)
|
||
|
||
~/.local/share/claude-scheduled/
|
||
└── logs/<task-name>/ # Timestamped .log files (last 30 kept)
|
||
```
|
||
|
||
Source files are in `~/dotfiles/claude-scheduled/` and symlinked into place.
|
||
|
||
## Three 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
|
||
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
|
||
```
|
||
|
||
### 3. Plain Bash Scripts
|
||
|
||
Used for tasks that don't need AI — just shell commands with their own systemd units.
|
||
|
||
```
|
||
sync-kb.timer (every 2 hours)
|
||
→ sync-kb.service
|
||
→ tasks/sync-kb/sync-kb.sh
|
||
→ git add/commit/push .md files in claude-home
|
||
→ push triggers kb-rag webhook reindex via Gitea Actions
|
||
```
|
||
|
||
Logs go to `~/.local/share/claude-scheduled/logs/sync-kb/` (last 30 kept).
|
||
|
||
## Active Tasks
|
||
|
||
| Task | Trigger | Type | Description |
|
||
|------|---------|------|-------------|
|
||
| `backlog-triage` | Weekdays 09:15 | runner.sh | Scan Gitea issues, prioritize, suggest focus |
|
||
| `sync-config` | Daily 02:00 | runner.sh | Commit and push `~/.claude` and `~/dotfiles` to Gitea |
|
||
| `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 |
|
||
| `sync-kb` | Every 2 hours | bash script | Commit and push claude-home KB changes to Gitea (triggers kb-rag reindex) |
|
||
|
||
## Settings Reference (`settings.json`)
|
||
|
||
```json
|
||
{
|
||
"model": "sonnet",
|
||
"effort": "medium",
|
||
"max_budget_usd": 0.75,
|
||
"allowed_tools": "Read(*) Glob(*) Grep(*)",
|
||
"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 |
|
||
| `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.15–0.30 per Sonnet run with MCP tools
|