<!--
name: 'Skill: /catch-up periodic heartbeat'
description: Skill definition for the /catch-up periodic heartbeat that scans current priorities, triages actionable changes, reports a short digest, and updates catch-up state
ccVersion: 2.1.161
-->
---
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 subagent. 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 deltas
  - `lastRunAt` — 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:

1. `CronList` — check whether a `/pre-meeting-checkin` for this event is already scheduled (by title match in the prompt). If yes, skip.
2. Pick a random offset 2–15 minutes before the local start time and `CronCreate` a 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` → now
- `lastSnapshot` → current state of each thing checked, for next pass's diff
- `priorities`:
  - **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.
