Skip to content

Claude Code: Pending-checks Ledger

A dated list of "check this later" items that Claude Code raises on the right day by itself, so nobody has to remember. It is three small pieces in your personal ~/.claude/: a rule, a markdown file and a SessionStart hook. The last section is a prompt you can paste to your own agent to set it up in a couple of minutes.

This is not a skill

Unlike the entries on Global Skills, nothing here is invoked by name. The hook fires on every session start and does nothing unless an item is due.

Why it exists

Claude Code memory recalls facts by relevance, never by date. A note like "check next Tuesday whether the backfill landed" only comes back if the conversation happens to touch that topic, so in practice it is lost.

Keeping a session open to wait is no better: it dies with the laptop, and background work started from a session dies with it. The ledger moves the "when" out of the model and into a hook that compares dates.

How it works

Piece Path Job
Rule ~/.claude/CLAUDE.md Tells the agent when to add an item and how to close one
Ledger ~/.claude/pending-checks.md The items, plus the format and lifecycle in its own header
Hook ~/.claude/hooks/pending-checks.sh, wired as SessionStart in ~/.claude/settings.json On session start, injects the items whose date has come

Each item is at most 5 lines:

### 2026-10-07 | COM-12345 | Does the live /companies page request first: 25?
check: open /companies on dev and read variables.first on the list request
pass: first is 25 and the page renders 25 rows
context: https://linear.app/credplatform/issue/COM-12345 (fix merged 10-06, deploy runs overnight)

The life of one item:

  1. You say "check this on Thursday". The agent writes a dated item and stops thinking about it.
  2. On Thursday the first session of the day starts and the hook finds the item.
  3. The agent answers your actual question first, then adds one line: this check is due, run it now?
  4. On yes, it runs check, reports the real result against pass, then deletes the item or moves its date.

  5. Later sessions on the same day stay quiet about it. A new or edited item is raised again.

  6. A malformed item (no date, more than 5 lines, an unparseable heading) is reported instead of being silently ignored.
  7. When nothing is due the hook prints nothing and never blocks a session.

Rules that keep it small

The first version grew to 88 KB and nagged in every session. Each rule fixes one thing that actually went wrong:

Rule What it fixed
Only park a check the user asked for The agent parked its own follow-ups and the list became a backlog
An item is at most 5 lines; the hook flags longer ones Items turned into a diary of measurements
Never append notes; history goes to the ticket Same diary problem, one comment at a time
Raise due items once a day, not once per session With ~10 sessions a day the same reminder came before every question
Answer the user's ask first, reminders last in one line Reminders pushed the real question down
An item re-dated twice with no progress gets deleted Checks nobody cared about lived forever
New work goes to Linear, never the ledger The ledger is for verifications that cannot run yet, nothing else

If you need a reminder at a precise minute with a push notification and nobody at the keyboard, use a scheduler (launchd, cron) instead. Everything else is a ledger line, which costs nothing and leaves no daemon behind.

Install it with your agent

Point Claude Code at this page, or paste the prompt below. Requires jq; nothing else. No tokens or external services are involved: commands inside a check: line run under your own credentials like any other agent command.

Read the "Claude Code: Pending-checks Ledger" wiki page and set it up in my personal
Claude Code config. Everything goes under ~/.claude/, never into a repository.

1. Create ~/.claude/hooks/pending-checks.sh from File 1 and chmod +x it.
2. Create ~/.claude/pending-checks.md from File 2. If it already exists, show it to me and stop.
3. Register the hook from File 3 in ~/.claude/settings.json. Merge it into the existing
   "hooks" object; do not drop any hook already there. Validate the JSON with jq.
4. Append the rule from File 4 to ~/.claude/CLAUDE.md (create the file if missing).
5. Test without touching the real stamp: add a throwaway item dated today, run
   PENDING_CHECKS_TODAY=$(date +%Y-%m-%d) PENDING_CHECKS_STAMP=/tmp/pc-test ~/.claude/hooks/pending-checks.sh
   and confirm the JSON names the item. Run it again and confirm it says the items were
   already raised. Then delete the throwaway item and /tmp/pc-test.
6. Tell me what you changed in each file.

File 1: ~/.claude/hooks/pending-checks.sh

#!/bin/bash
# SessionStart hook: surface dated verification items whose time has come.
#
# Why this exists: memory recall fires on RELEVANCE, never on a DATE, so
# "check this next week" reliably drowns there. This reads
# ~/.claude/pending-checks.md and injects only the items that are due.
#
# Once a day, not once a session (2026-10-05): users run many sessions a day and
# the same items were pushed ahead of the question in every one of them. The
# first session of the day raises them; later sessions get a one-line note
# telling the model NOT to raise them. The stamp records today's date plus a
# hash of the due set, so a newly due or edited item is raised again.
#
# Silent when nothing is due. Never blocks, never fails a session.
#
# Testing: PENDING_CHECKS_TODAY=YYYY-MM-DD PENDING_CHECKS_STAMP=/tmp/x ~/.claude/hooks/pending-checks.sh
set -uo pipefail

LEDGER="$HOME/.claude/pending-checks.md"
STAMP="${PENDING_CHECKS_STAMP:-$HOME/.claude/.pending-checks-raised}"
MAX_LINES=5   # header + check + pass + context, one blank-free block
[ -r "$LEDGER" ] || exit 0
command -v jq >/dev/null 2>&1 || exit 0

TODAY="${PENDING_CHECKS_TODAY:-$(date +%Y-%m-%d)}"

# Due items: a `###` line whose date is <= today, plus its check/pass/context lines.
due=$(awk -v today="$TODAY" '
  /^### / {
    line = substr($0, 5)
    split(line, parts, " | ")
    d = parts[1]
    gsub(/^[ \t]+|[ \t]+$/, "", d)
    emit = (d ~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/ && d <= today)
    if (emit) {
      overdue = (d < today) ? " [OVERDUE since " d "]" : " [due today]"
      print "- " substr(line, index(line, "|") + 2) overdue
    }
    next
  }
  emit && /^(check|pass|context):/ { print "    " $0 }
  /^$/ { emit = 0 }
' "$LEDGER")

# Items the parser cannot use, so a bad one is loud instead of invisible:
#  - a `###` header whose first field is not a date (e.g. "SETTLED ...", left undeleted)
#  - an item longer than MAX_LINES (the ledger grew to 88 KB of diary that way)
problems=$(awk -v max="$MAX_LINES" '
  function flush() { if (h != "" && n > max) print "  TOO LONG (" n " lines, max " max "): " h; h = ""; n = 0 }
  /^## Open items/ { open = 1; next }
  !open { next }
  /^### / {
    flush(); h = substr($0, 5); n = 1
    split(h, p, " | "); d = p[1]; gsub(/^[ \t]+|[ \t]+$/, "", d)
    if (d !~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/) print "  NO DATE (settled? delete it): " h
    next
  }
  /^## / { flush(); print "  UNPARSEABLE heading: " substr($0, 4); next }
  /^$/ { flush(); next }
  h != "" { n++ }
  END { flush() }
' "$LEDGER" | cut -c1-160)

[ -z "$due" ] && [ -z "$problems" ] && exit 0

key="$TODAY $(printf '%s\n%s' "$due" "$problems" | shasum | cut -c1-12)"
if [ -r "$STAMP" ] && [ "$(cat "$STAMP" 2>/dev/null)" = "$key" ]; then
  n=$(printf '%s\n' "$due" | grep -c '^- ')
  msg="Pending-checks ledger: $n item(s) due today were already raised in an earlier session today. Do NOT raise them again; mention them only if the user asks."
else
  printf '%s\n' "$key" > "$STAMP" 2>/dev/null
  msg="Dated checks are due from the pending-checks ledger (~/.claude/pending-checks.md). Answer the user's own message first; then mention these in ONE short line at the end of your reply and ask whether to run them now. Do not put them ahead of the user's ask.

$due

When one is run: report the real number against its pass line, then delete the item or change its due date. Never append notes to it; history belongs in the ticket."
  [ -n "$problems" ] && msg="$msg

LEDGER PROBLEMS (fix them when you touch the file: delete settled items, move history to the ticket, keep each item to 5 lines):
$problems"
fi

jq -n --arg m "$msg" '{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: $m}}'
exit 0

File 2: ~/.claude/pending-checks.md

# Pending checks

Dated reminders for a FUTURE session. Not a backlog, not a to-do list, not a diary.
An item belongs here only if the user asked to park it, it is a verification that
cannot run yet, and it has a date when it can.

Read by ~/.claude/hooks/pending-checks.sh on SessionStart. Due items are raised in
the FIRST session of the day only; later sessions that day stay quiet.

## Format: one block per item, at most 5 lines including the header

    ### <due YYYY-MM-DD> | <ticket or tag> | <the question in one line>
    check: <the exact command or query, ONE line>
    pass: <what a good answer looks like, ONE line>
    context: <ticket link + the one fact the next session needs, ONE line>

## Lifecycle

1. Mention due items in one line, after the user's own ask is done.
2. Run the check, report the real result against pass.
3. Delete the item if settled, or change its due date if "not yet". Do not append notes.
4. New work goes to Linear. A check re-dated twice without progress gets deleted.

## Open items

File 3: hook entry for ~/.claude/settings.json

Merge into the existing hooks object:

{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command", "command": "$HOME/.claude/hooks/pending-checks.sh" } ] }
    ]
  }
}

File 4: rule to append to ~/.claude/CLAUDE.md

### Something to verify LATER: park it in the pending-checks ledger (only when I ask)

When I ask to check something later ("did the rebuild land", "did the number move
after Tuesday's job"), do NOT hold a session open for it and do NOT trust memory to
surface it: memory recall fires on relevance, never on a date. Add it to
~/.claude/pending-checks.md as a 5-line item: header with due date, one check: line,
one pass: line, one context: line with the ticket link. Do not park follow-ups I did
not ask for.

The SessionStart hook raises due items once a day, in the first session, as one line
after my own ask. Whoever picks one up runs the check, reports the real result, then
deletes the item or changes its date. Never append notes to an item: measurements and
corrections go in the ticket.