6.1 KiB
name: catch-up description: Periodic heartbeat — figure out what matters to the user right now, check the state of those things, and decide whether to surface an update, propose an action, or stay quiet. user-invocable: true context: fork
Catch-Up
This fires every two hours (schedule lives in .claude/scheduled_tasks.json — narrow the cron's hour range once the user's Catch-up hours are known, e.g. 0 9-17/2 * * *, to cut idle wake-ups; leave day-of-week at * so Quiet Hours stays the single source of truth for weekday filtering). Runs in a forked sub-agent. Your job: figure out what matters to the user right now, check on those things, and return a digest. The main agent receives your final text as the result and decides whether to relay it.
Silence is the default. Only surface something if it's actionable, time-sensitive, or you could take it off their plate. A noisy catch-up trains the user to ignore you.
You don't see the main agent's conversation — and that's fine. Your job is to surface what they're not already looking at. If they're mid-task on something, they know about it; you're looking for the blindside.
Quiet Hours
First: check the time. CLAUDE.md has a Catch-up hours field under Schedule (their timezone is also there). Default is 9am–5pm Mon–Fri if unset.
Outside that window → update lastRunAt in .claude/catch-up-state.json and end with a single line:
(quiet hours)
Don't scan. The main agent will see this and not relay.
Exception: a priority in the state file flagged checkAlways: true (something genuinely time-critical — an incident they're on-call for) gets checked regardless.
Phase 1 — Orient
Figure out what matters.
- Who are they? Read
CLAUDE.md— job, focus areas, the handles that identify them in connected tools. - What are you tracking? Read
.claude/catch-up-state.json:priorities— things you're watching (work in flight, a conversation they're waiting on, a deadline)lastSnapshot— last known state of each, for computing deltaslastRunAt— when you last checked, for time-scoped queries
- What tools are connected? Look at what's actually available in your context. Don't assume a set — adapt.
If priorities is empty (first run), bootstrap a small list from CLAUDE.md + connected tools. Two or three things. The list refines itself over time.
Phase 2 — Scan
Scan what's in priorities, not everything. Don't sweep all connected tools every pass — that's expensive and noisy. The state file's priorities list is your scope. If it has three things, check those three.
For each priority: has this changed in a way that matters since last check? Compare against lastSnapshot.
The palette below is where priorities come from (what kinds of things you might track), not what to scan every pass:
- Source control & CI — their open PRs/MRs, review requests, CI status, issues assigned. GitHub via
gh, GitLab, etc. - Chat — mentions, DMs, threads they're in. Slack, Teams, Discord.
- Email — unread from people or domains that matter.
- Calendar — what's coming up soon, anything that moved since last check.
- Documents & wikis — new comments or edits on things they own or are tagged in. Drive, Docs, Notion, Confluence.
- Issue tracking — tickets assigned, status changes on things they watch. Linear, Jira, GitHub Issues.
Since you're running in a fork, do the scan directly — no need to delegate further.
Calendar sync
If a calendar tool is connected: pull events for the rest of today and look for anything new or moved since lastRunAt. Morning-checkin scheduled pre-meeting check-ins for everything it knew about at start of day, but events get added. For each new event with a concrete start time still in the future:
CronList— check whether a/pre-meeting-checkinfor this event is already scheduled (by title match in the prompt). If yes, skip.- Pick a random offset 2–15 minutes before the local start time and
CronCreatea one-shot (recurring: false) with prompt/pre-meeting-checkin <title> · <local time> · <attendees> · <doc links>.
This keeps pre-meeting coverage current without the user doing anything. Tool calls from a fork execute (CronCreate writes to disk) — main agent just doesn't see the result blocks. Don't mention scheduled check-ins in your digest; they'll fire on their own.
Phase 3 — Triage
Sort findings into dispositions:
- assistant-can-act — You could handle it without bothering them. Failing build with an obvious fix. A small review to draft.
- user-should-act — Only they can decide. Needs their judgement, approval, presence.
- fyi — Informational, not urgent. Worth knowing but not worth an interrupt.
- suppress — Already reported last pass, or below noise floor.
A surface that churns constantly needs a higher bar than one that's usually quiet.
Phase 4 — Report
Your final text is the result the main agent receives. Format:
Nothing actionable:
Nothing actionable.
Main agent won't relay this.
Something to surface:
· <user-should-act item> — <what they need to act: link, name, time>
· <assistant-can-act item> — I can <proposed action>. Say go.
Urgency first. Three bullets max. If there's more, your noise floor is too low or your priorities list is too wide.
Phase 5 — Learn
Before ending, write back to .claude/catch-up-state.json:
lastRunAt→ nowlastSnapshot→ current state of each thing checked, for next pass's diffpriorities:- Promote — new things worth tracking that you discovered. Note why, and an expiry if time-bound.
- Prune — things that resolved or expired.
- Demote — things unchanged across several passes. Drop or check less often.
This file is how catch-up gets smarter. Doesn't have to be perfect, just useful.