Click: Recurring Tasks for Claude
I wanted a super easy way to have Claude handle recurring organisational tasks for me, tracked across the many different places on my system. I already organize everything in Obsidian, so a distributed markdown-based approach felt natural.
So I built Click: define a task once in a CLICK.md file wherever you need it, and Claude runs it on schedule. One central registry orchestrates tasks scattered across your machine, each folder owning its own definition.
The Architecture
~/.clicks.md ← Central registry (tracks all tasks)
│
├──► ~/notes/CLICK.md ← Categorize daily logs
│
├──► ~/work/project/CLICK.md ← Sync docs from Confluence
│
├──► ~/dotfiles/CLICK.md ← Update README from configs
│
└──► ~/reading/CLICK.md ← Archive old articlesOne registry orchestrates many tasks. Each CLICK.md is self-contained with absolute paths, so Claude can run everything from your home directory.
The Registry
Lives at ~/.clicks.md:
# Clicks
## Active
| Path | Frequency | Last Run | TTL Expires | Status |
|------|-----------|----------|-------------|--------|
| ~/notes/CLICK.md | daily | 2026-01-30 | 2026-04-30 | ok |
| ~/work/project/CLICK.md | weekly | 2026-01-27 | 2026-03-15 | ok |
| ~/dotfiles/CLICK.md | monthly | 2026-01-01 | 2026-06-01 | ok |Local Task Files
Each folder defines what it needs in a CLICK.md:
# Sync Project Docs from Confluence
**Inputs:**
- Confluence space: PROJECT
**Outputs:**
- /Users/.../work/project/docs/
**Task:**
Fetch pages from Confluence space modified after {{last_run}}.
Update corresponding local markdown files.The {{last_run}} placeholder gets the timestamp from the registry. Each run only processes changes since then.
Running It
Say "run clicks" and Claude checks what's due:
$ claude
╭─────────────────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code! │
╰─────────────────────────────────────────────────────────╯
> run clicks
CLICKS RUN COMPLETE
===================
Ran 2 clicks: 2 succeeded, 0 failed
Categorize Logs: processed 5 entries
Sync from Confluence: updated 2 filesTTL: Tasks That Expire
Every task has an expiration date. When it hits:
Click "Sync Docs" expired. Renew for 3 months, or archive?No zombie automations running forever. If it's still useful, renew it.
Session Hook
Why not run clicks automatically in the background? Because I want to approve what Claude does to my files. Automated background execution means something modifies your stuff without you knowing. With Click, nothing runs unless you explicitly say "run clicks."
A shell hook reminds you about overdue tasks when you start Claude:
$ claude
[clicks] 2 due: Categorize Logs, Sync Docs. Run 'run clicks' when ready.The reminder fires once per day, not every session. The registry tracks the last reminder timestamp to avoid nagging. But execution is always manual. You decide when to run, you see what happens, you stay in control.
Commands
| Command | What it does |
|---|---|
run clicks |
Execute all due tasks |
show clicks |
Display status |
add click |
Create new task (interactive) |
renew click |
Extend TTL |
remove click |
Delete a task |
Setup
- Create skill at
~/.claude/skills/click/SKILL.md - Create registry at
~/.clicks.md - Add
CLICK.mdfiles wherever you need recurring work - (Optional) Add a
SessionStarthook in~/.claude/settings.jsonfor daily reminders
Example Use Cases
- Categorize daily logs into organized notes
- Sync docs from Confluence/Notion to local
- Generate changelogs from commits
- Extract action items from meeting notes
- Update documentation from code changes
- Archive and clean up old files
The task files are just markdown. While this post shows Claude Code, any AI assistant that can read files and follow instructions could run clicks with equivalent setup.
Full Skill File (click to expand)
Save this as ~/.claude/skills/click/SKILL.md:
---
name: click
description: "Use when managing recurring tasks - run clicks, add click, show clicks, edit click, remove click, renew click. Orchestrates distributed CLICK.md files from central ~/.clicks.md registry."
---
# Click - Distributed Recurring Tasks
## Overview
Click manages recurring tasks across your system. Each task is defined in a local `CLICK.md` file, orchestrated from a central registry at `~/.clicks.md`.
## Commands
| Command | Description |
|---------|-------------|
| `run clicks` | Execute all due clicks |
| `run click [path]` | Run specific click regardless of schedule |
| `show clicks` | Display registry status |
| `add click` | Interactive flow to create new click |
| `edit click [path]` | Modify existing click |
| `remove click [path]` | Archive/delete a click |
| `renew click [path]` | Extend TTL for expired click |
## Central Registry (`~/.clicks.md`)
The registry tracks all clicks with metadata:
# Clicks
Last reminder: YYYY-MM-DDTHH:MM:SS
## Active
| Path | Frequency | Last Run | TTL Expires | Status |
|------|-----------|----------|-------------|--------|
## Expired
| Path | Frequency | Last Run | Expired On |
|------|-----------|----------|------------|
## Determining "Due"
| Frequency | Due if last run was... |
|-----------|------------------------|
| daily | > 24 hours ago |
| weekly | > 7 days ago |
| monthly | > 30 days ago |
Special: `Last Run: -` (never run) is always due.
## Local Click File Format (`CLICK.md`)
# [Descriptive Name]
**Context:** Brief description of workspace/project
**Inputs:**
- /absolute/path/to/source.md
- /absolute/path/to/folder/
**Outputs:**
- /absolute/path/to/destination.md
- /absolute/path/to/folder/
**Task:**
[Natural language instructions]
Read inputs for entries since {{last_run}}.
[Processing logic...]
**Requirements:**
- All paths MUST be absolute (executable from anywhere)
- `{{last_run}}` placeholder gets substituted with timestamp
- Outputs can be files OR directories
- When output is directory, read its CLAUDE.md to understand structure
## "run clicks" Flow
1. Read `~/.clicks.md`
2. Parse Active table
3. Check for expired TTLs first, ask user to renew or archive each
4. For each non-expired entry:
- Check if due (frequency vs last run date)
- If due: read local CLICK.md, execute task, update Last Run
- If failed: mark status as 'failed', continue with others
5. Report summary: "Ran N clicks: X succeeded, Y failed"
6. For each failure: ask user how to proceed
## "add click" Flow
1. Ask: "Where should this click live?" (directory path)
2. Ask: "What should this task do?" (natural language)
3. Ask: "What are the inputs?" (files/folders to read)
4. Ask: "What are the outputs?" (files/folders to update)
5. Ask: "How often? (daily/weekly/monthly)"
6. Ask: "When should this expire?" (TTL date)
7. Create local CLICK.md with absolute paths
8. Add row to ~/.clicks.md Active table
9. Confirm creation
## Bootstrap
First use of click commands creates `~/.clicks.md` if it doesn't exist.
## Error Handling
When a click fails:
1. Mark status as 'failed' in registry
2. Continue with remaining clicks
3. At end of run, ask about each failure
4. User decides: retry, skip, edit, or removeSession Hook Script (click to expand)
Save as ~/.claude/hooks/click-reminder.sh and make executable (chmod +x):
#!/bin/bash
# Click reminder hook - once-daily reminder for due clicks
CLICKS_FILE="$HOME/.clicks.md"
[[ ! -f "$CLICKS_FILE" ]] && exit 0
# Check if 24h since last reminder
last=$(grep "^Last reminder:" "$CLICKS_FILE" | cut -d' ' -f3-)
[[ -z "$last" || "$last" == "-" ]] && last="1970-01-01T00:00:00"
last_ts=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$last" "+%s" 2>/dev/null || echo 0)
(( $(date +%s) - last_ts < 86400 )) && exit 0
# Parse Active table, collect due clicks
due=()
now=$(date +%s)
in_active=false
while IFS= read -r line; do
[[ "$line" == "## Active" ]] && in_active=true && continue
[[ "$line" == "## "* ]] && in_active=false
$in_active || continue
[[ "$line" != "|"* || "$line" == *"Path"* || "$line" == *"---"* ]] && continue
path=$(echo "$line" | cut -d'|' -f2 | xargs)
freq=$(echo "$line" | cut -d'|' -f3 | xargs)
last_run=$(echo "$line" | cut -d'|' -f4 | xargs)
[[ -z "$path" ]] && continue
name=$(basename "$(dirname "$path")")
if [[ "$last_run" == "-" ]]; then
due+=("$name (new)")
else
days=$(( (now - $(date -j -f "%Y-%m-%d" "$last_run" "+%s" 2>/dev/null || echo 0)) / 86400 ))
case "$freq" in
daily) (( days >= 1 )) && due+=("$name") ;;
weekly) (( days >= 7 )) && due+=("$name") ;;
monthly) (( days >= 30 )) && due+=("$name") ;;
esac
fi
done < "$CLICKS_FILE"
# Output if any due
if (( ${#due[@]} > 0 )); then
echo "[clicks] ${#due[@]} due: $(printf '%s, ' "${due[@]}" | sed 's/, $//'). Run 'run clicks' when ready." >&2
sed -i '' "s/^Last reminder:.*/Last reminder: $(date +%Y-%m-%dT%H:%M:%S)/" "$CLICKS_FILE"
fiThen add to ~/.claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "/path/to/click-reminder.sh"
}
]
}
]
}
}