A tenth review pass, after confirming all nine prior rounds of fixes
hold up under independent re-reading, found two more low-severity
gaps and offered to accept a follow-up -- fixed both now for
consistency with how every prior round's findings were handled:
1. schedule had no confirmation gate at the CLI layer. The "confirm
with the user before schedule" safeguard (deviation #15) lived only
in commands/skillopt-sleep.md's agent-facing instructions --
cmd_schedule() called scheduler.schedule() directly and installed a
real crontab entry immediately. Fine for the documented Claude Code
agent workflow (which confirms in chat first), but anyone invoking
`python -m skillopt_sleep schedule` directly bypassed it entirely.
Fixed: schedule now requires --yes; an interactive terminal without
it gets a [y/N] prompt, a non-interactive one refuses outright
(exit 2) pointing at --yes. commands/skillopt-sleep.md updated so
the driving agent passes --yes once it has confirmed with the user
in chat -- that's what --yes records, not a redundant re-prompt
that would hang forever with no TTY inside a non-interactive Bash
tool call.
2. mkdir-then-chmod wasn't atomic in write_staging()/SleepState.save(),
leaving a brief window where a freshly-created sensitive directory
sat at the process's default umask. Fixed: the os.makedirs() calls
creating the state dir, staging leaf dir, and backup dir now pass
mode=0o700 directly, on top of (not instead of) the existing
post-creation chmod calls, which still matter for intermediate
parent dirs and pre-existing directories that mode= doesn't cover.
The equivalent race for individual files was judged a larger
rewrite (every open() call site would need os.open() with an
explicit mode) than this specific low-severity finding warranted --
documented as a known, narrower residual gap rather than silently
claimed as fully closed.
Verified: non-interactive schedule without --yes refuses with exit 2,
with --yes it proceeds to the same scheduler.schedule() call as
before; a synthetic run confirms state dir/state.json/staging leaf
still land at 0700/0600/0700 after the mode= change.
Added as README deviations #22-23 and reconciled the count across all
three documents to 23 (6 cosmetic, 17 safety/hardening) across ten
review rounds -- cross-checked with grep.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TX374i2YGrjNV4Yi3AmaKS
A fourth automated review pass on PR #907 found the deviation count had
drifted out of sync across the three places that document it, plus two
more real gaps in the vendored plugin:
1. Deviation count inconsistency: plugin.json and README.md both said
13 (after round 3), but CLAUDE.md's v2.11.2 section said "8
deviations" with an itemized list that didn't map onto the real
13-item README list -- it named a "dead cross-reference to a
non-vendored design doc" as a cosmetic item that was never actually
added as a numbered README deviation (it was fixed in round 3's
commit but never itemized). Fixed: added it as README deviation
#14, updated plugin.json's derivation_note with a note that
README.md's numbered list is the single source of truth if any
summary disagrees again, and rewrote CLAUDE.md's bullet to match.
2. commands/skillopt-sleep.md's action table listed `schedule` as an
ordinary action alongside safe previews (`status`/`dry-run`/`run`),
while its own "Safety reminders" section separately said to point
users at the print-only install-cron.sh instead -- two
uncoordinated stories about the same action. scheduler.schedule()
writes directly to the user's real crontab the moment it runs, with
no confirmation step. Fixed (README deviation #15): "Steps to
follow" now has an explicit step 1 telling the agent to confirm
with the user before running `schedule`; "Safety reminders" no
longer contradicts the action table.
3. state.json (the cross-night task archive) and
.skillopt-sleep/staging/<ts>/'s proposal/report/diagnostics files
contain real harvested session content in plaintext, created via
plain os.makedirs/open(...,"w") -- world-readable-by-default on a
typical multi-user box. Fixed (README deviation #16): state.py and
staging.py now chmod every directory they create to 0700 and every
file they write to 0600 (best-effort). Live CLAUDE.md/SKILL.md
files are intentionally left alone -- those are the user's own,
often-committed files, not new output this plugin introduces.
All three documents (README.md's numbered list, plugin.json's
derivation_note, CLAUDE.md's v2.11.2 section) now agree on 16
deviations (3 cosmetic, 13 safety/hardening) -- verified by grep.
Verified: py_compile clean, mock-backend dry-run still exits 0, a
synthetic test confirms state dir/state.json/staging dir/staging files
land at 0700/0600/0700/0600 respectively after this fix (previously
default umask permissions), all 4 repo CI gates pass locally.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TX374i2YGrjNV4Yi3AmaKS
Verbatim copy of the stdlib-only skillopt_sleep engine + Claude Code
plugin surface (skills/hooks/commands/scripts) into
engineering/skillopt-sleep/. Gives a local agent a nightly gated
self-improvement cycle: read-only harvest of past Claude Code session
transcripts -> mine recurring tasks -> offline replay -> held-out-gated
CLAUDE.md/SKILL.md edits -> staged for explicit /skillopt-sleep adopt.
Nothing live changes without that explicit step.
The heavier skillopt training package (needs numpy/openai/azure-* +
hand-labeled benchmarks per task) was deliberately not vendored, since
it optimizes one narrow scoreable task at a time and doesn't fit this
repo's broad domain-expertise skills or no-ML-in-scripts convention.
Attribution preserved in plugin.json + LICENSE + README.md (MIT,
Microsoft Corporation / Yifan Yang), following the same verbatim-vendor
pattern already used for loop-library/. Registered as its own
marketplace plugin; headline counters in README.md/CLAUDE.md/
marketplace.json trued up via scripts/derive_counters.py --check.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TX374i2YGrjNV4Yi3AmaKS