claude-skills/engineering/skillopt-sleep/scripts/install-cron.sh
Claude a227b06e6f
fix(engineering): anchor scheduler.py's project marker match (round-9 bug)
A ninth review pass found scheduler.py's schedule()/unschedule() both
located "this project's" managed cron line via marker not in ln, a
bare substring test, not an exact-match or delimiter-anchored check.

Failure scenario: two projects scheduled where one path is a literal
prefix of the other (e.g. /home/user/app and /home/user/app-v2) --
"# project=/home/user/app" is itself a substring of
"# project=/home/user/app-v2"'s line. Running schedule() or
unschedule() for /home/user/app would silently drop app-v2's cron
entry too, with no error or warning.

harvest.py's _project_matches() (added in this same PR) already gets
this right via delimiter-anchored comparison; scheduler.py's marker
matching didn't follow the same discipline.

Fixed: added _line_matches_project(), anchored on
ln.rstrip().endswith(marker) since the marker is always the last token
of a generated line -- used at both call sites.

Also fixed the related minor nit: install-cron.sh's printed --backend
value was unquoted next to otherwise-quoted ${RUNNER}/${PROJECT} in
its heredoc (low risk since that script only prints a line for the
user to copy, never executes anything itself, but inconsistent with
the quoting discipline everywhere else).

Verified two ways: a standalone reproduction confirmed the bug before
the fix and its absence after, and a full schedule()/unschedule()
round-trip through the actual public API (crontab -l/crontab - swapped
for an in-memory fake) confirmed scheduling both /home/user/app and
/home/user/app-v2, then unscheduling only app, correctly leaves
app-v2's line intact.

Added as README deviations #20-21 and reconciled the count across all
three documents to 21 (6 cosmetic, 15 safety/hardening) across nine
review rounds -- cross-checked with grep.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TX374i2YGrjNV4Yi3AmaKS
2026-07-11 19:32:33 +00:00

29 lines
1.5 KiB
Bash
Executable file

#!/usr/bin/env bash
# Print (does NOT install) a crontab line that runs SkillOpt-Sleep nightly.
# The user copies the line into `crontab -e` if they want it.
set -euo pipefail
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
RUNNER="$PLUGIN_ROOT/scripts/sleep.sh"
PROJECT="${1:-$(pwd)}"
BACKEND="${2:-mock}"
# 3:17am local — deliberately off the :00 mark so many users don't all hit the
# API at once (and we leave room for jitter).
MIN=17
HOUR=3
cat <<EOF
# ── SkillOpt-Sleep nightly cycle ────────────────────────────────────────────
# Review past sessions, replay tasks, stage validated memory/skill updates.
# Runs at ${HOUR}:$(printf '%02d' $MIN) local every day. Output goes to the project's
# .skillopt-sleep/ dir; nothing live is changed until you run '/skillopt-sleep adopt'
# (unless you pass --auto-adopt below).
#
# Copy the next line into 'crontab -e':
${MIN} ${HOUR} * * * "${RUNNER}" run --project "${PROJECT}" --scope invoked --backend "${BACKEND}" >> "${PROJECT}/.skillopt-sleep/cron.log" 2>&1
#
# For fully-autonomous adoption (power users), append: --auto-adopt
# To spend real API budget for genuine lift, set BACKEND=anthropic above.
# ────────────────────────────────────────────────────────────────────────────
EOF