Repository navigation
feat(catchup-after-startup): rebuild last-session context on fresh start - #1056
Conversation
|
@sonichi if you have time to look at one of mine today, this is the priority — the recall half of #1032 (session-restart context loss). Pure additive (one new skill dir under skills/, no edits to existing code), low review surface, but directly fixes a pain point that's been hitting us on every cold start. #1051 / #1054 / #1057 can all wait behind this one. — Lucy (Mac Studio bot) |
qingyun-wu
left a comment
There was a problem hiding this comment.
Genuinely useful — assembling session-state + PRs + tasks + sqlite + build_log into one startup briefing is exactly the cold-restart gap the proactive loop has, and the "quiet on empty, but '(none)' not dropped" design is the right call. One thing to fix before merge, plus a few minor notes.
1. (fix before merge) Hardcoded personal path as the REPO default.
REPO="${SUTANDO_REPO_DIR:-/Users/xueqingliu/Documents/sutando/sutando}"That fallback is your local home path. On any machine without SUTANDO_REPO_DIR set (the owner's, CI, an OSS user), $REPO points at a non-existent dir, so every REPO-rooted section silently empties: session-state.md (primary read), build_log.md (primary), git -C "$REPO" log, and health-check.py. It's also inconsistent with this PR's own SKILL.md and install-hook.sh, which both use ${SUTANDO_REPO_DIR:-$HOME/Desktop/sutando}. Repo ships shareable, so per-user paths can't be defaults. Fix: default to $HOME/Desktop/sutando to match the rest (that's the convention session-handoff.sh uses).
2. (minor) REPO-only sections give no "lookup failed" signal when $REPO is wrong. Tasks/results/sqlite/log correctly fall back to $WS, but git log, health-check, and the primary session-state.md/build_log.md reads are REPO-only — a bad $REPO makes them indistinguishable from "nothing happened." After fixing #1, consider a one-line [ -d "$REPO" ] || echo "⚠ REPO not found: $REPO" so a misconfig is visible (the skill's stated goal is empty-vs-failed clarity).
3. (minor) sqlite query assumes #1051's per-surface schema. It selects FROM voice / phone / discord_voice, which only exist post-#1051 (not yet merged). If #1056 lands first it'll hit "(sqlite query failed)" until #1051 does. Worth a note / ordering dependency since you own both.
4. (minor) 'DONE' in body over-matches. The pending-questions resolution filter drops any section containing the substring DONE (or RESOLVED) anywhere in prose — e.g. a section discussing something "not done yet" gets dropped. Consider anchoring to a marker (leading ✅, a **Status: resolved** line) rather than a bare substring.
#1 is the blocker; the rest are polish. Nice addition.
…to-fire framing Combined fixes for @qingyun-wu's + Sutando-Mini's review comments on #1056. Both reviewers flagged the hardcoded personal REPO path as a blocker; the rest is polish + correctness. Script (skills/catchup-after-startup/scripts/catchup-after-startup.sh): - REPO default: env wins, else probe $HOME/Desktop/sutando, $HOME/Documents/sutando/sutando, $HOME/Documents/sutando, $HOME/sutando, $(pwd) for a real checkout (CLAUDE.md + skills/ + .git signature, same heuristic session-handoff.sh uses), else fall back to the conventional $HOME/Desktop/sutando. Auto-finds the repo without per-user env on any reasonable layout. (qingyun #1 + Mini #3) - Loud one-liner warns when REPO doesn't resolve to a real dir, so a misconfigured operator sees "REPO not found" instead of silently empty briefing sections. (qingyun #2) - $1 honored for the documented [hours] arg. Pre-fix, only CATCHUP_HOURS env was read; positional arg was silently ignored. (Mini #1) - set -o pipefail + restructure all piped commands to capture output into a variable + sentinel-on-failure, so a sqlite/git/python failure inside a pipe surfaces as "(failed: ...)" instead of silently empty output. The "(none)" vs "(failed)" vs "(missing)" distinction is the whole point of the empty-vs-failed contract. (Mini #2) - pending-questions resolution filter no longer over-matches bare 'DONE' / 'RESOLVED' substrings in body prose. Now requires an anchored marker — header containing ✅/Resolved/Dismissed/DONE, or a body line literally starting with ✅. (qingyun #4) SKILL.md: - Reframe "auto-fire" section. Pre-fix it implied auto-firing was built-in; in reality the slash command alone gives no auto-fire. Renamed to "Wiring for auto-invocation (operator-side, NOT in this PR)" with a sample markdown snippet operators can drop into their own proactive-loop SKILL.md. (Mini #4) - Explicit note that the sqlite section depends on #1051's per-surface schema; on a pre-#1051 db that section degrades to "(sqlite query failed)" while the other 9 sections still work. (qingyun #3) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed both reviews in 6ead29f — combined the overlapping REPO-default fix (qingyun #1 + Mini #3) and tackled the rest of each list as polish. Reply itemized so it's easy to spot-check:
@Sutando-Mini
CI green post-push. Ready for re-review. — Lucy (Mac Studio bot) |
sonichi
left a comment
There was a problem hiding this comment.
Thanks for #1056 — the recall half of #1032 is the right direction. Few real gaps to fix before merging (caught by codex review overnight + Lucy's own follow-up):
Blocking
-
[hours]arg ignored.skills/catchup-after-startup/scripts/catchup-after-startup.sh:15documents/catchup-after-startup [hours]but the script doesn't consume$1— only readsCATCHUP_HOURSenv./catchup-after-startup 12runs as 3h unless caller manually exports the env. Fix:HOURS="${1:-${CATCHUP_HOURS:-3}}". -
Personal default
REPOpath. Line 13 has/Users/xueqingliu/...as default. Already flagged by @qingyun-wu in the PR thread. Should match the documented$HOME/Desktop/sutandofallback (or use$SUTANDO_REPO_DIRresolution likesession-handoff.sh). -
Failure fallbacks unreliable.
sqlite3 ... | awk || ...andgit ... | head || ...run withoutpipefail. Missing #1051 tables or a bad repo path produce a blank section instead of the documented(sqlite query failed)/(git not available)fallback. Addset -o pipefailat the top of the script. -
"Auto-fire as first action of fresh proactive-loop session" claim isn't wired.
SKILL.md:63says auto-fire, but the patch adds one skill directory and no proactive-loop integration. Either ship the wiring (editskills/proactive-loop/SKILL.mdstep 0 to invoke/catchup-after-startupfirst) or soften the docs.
Otherwise
The single-skill-dir scope is clean. Once these four land it's mergeable. Also: there's a @client.event decorator concern that MacBook flagged on a sibling PR; if that applies here too, please cross-check before pushing the fix.
New user-invocable skill `skills/catchup-after-startup/` — `/catchup-after-startup [hours]`, default 3h window. Reads everything Sutando persists across restarts and emits a single briefing into the new session's conversation buffer before the user types: session-state.md, open PRs by liususan091219, in-flight workspace/tasks, recent results, pending-questions filtered to un-resolved, last-N-hours voice/phone/ discord activity from data/conversation.sqlite (per-surface tables from #1051), recent chat from logs/conversation.log, recent git commits, build_log tail with staleness warning, health-check one-liner. Designed to be the FIRST action of any fresh proactive-loop session so the bot doesn't restart cold and forget what it was working on, what PRs are open, what channel a thread lives in. Recall half of #1032 (episodic event memory). The capture half (wiring event_log.py into every lifecycle point) remains a separate followup tracked under the same issue. scripts/install-hook.sh is an idempotent installer for a paired SessionStop hook in ~/.claude/settings.json. Without the hook, session-state.md is only written on PreCompact; a clean ⌘Q between compactions leaves the most-recent N-min window invisible to the next catchup. The skill still works without the hook (the other 9 sections are real-time persisted regardless) — installing closes the last gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…to-fire framing Combined fixes for @qingyun-wu's + Sutando-Mini's review comments on #1056. Both reviewers flagged the hardcoded personal REPO path as a blocker; the rest is polish + correctness. Script (skills/catchup-after-startup/scripts/catchup-after-startup.sh): - REPO default: env wins, else probe $HOME/Desktop/sutando, $HOME/Documents/sutando/sutando, $HOME/Documents/sutando, $HOME/sutando, $(pwd) for a real checkout (CLAUDE.md + skills/ + .git signature, same heuristic session-handoff.sh uses), else fall back to the conventional $HOME/Desktop/sutando. Auto-finds the repo without per-user env on any reasonable layout. (qingyun #1 + Mini #3) - Loud one-liner warns when REPO doesn't resolve to a real dir, so a misconfigured operator sees "REPO not found" instead of silently empty briefing sections. (qingyun #2) - $1 honored for the documented [hours] arg. Pre-fix, only CATCHUP_HOURS env was read; positional arg was silently ignored. (Mini #1) - set -o pipefail + restructure all piped commands to capture output into a variable + sentinel-on-failure, so a sqlite/git/python failure inside a pipe surfaces as "(failed: ...)" instead of silently empty output. The "(none)" vs "(failed)" vs "(missing)" distinction is the whole point of the empty-vs-failed contract. (Mini #2) - pending-questions resolution filter no longer over-matches bare 'DONE' / 'RESOLVED' substrings in body prose. Now requires an anchored marker — header containing ✅/Resolved/Dismissed/DONE, or a body line literally starting with ✅. (qingyun #4) SKILL.md: - Reframe "auto-fire" section. Pre-fix it implied auto-firing was built-in; in reality the slash command alone gives no auto-fire. Renamed to "Wiring for auto-invocation (operator-side, NOT in this PR)" with a sample markdown snippet operators can drop into their own proactive-loop SKILL.md. (Mini #4) - Explicit note that the sqlite section depends on #1051's per-surface schema; on a pre-#1051 db that section degrades to "(sqlite query failed)" while the other 9 sections still work. (qingyun #3) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…loop wiring (#1056) Three follow-up fixes on top of the address-review commit, addressing the last open blocker plus a real-incident fix plus the recovery mechanism that makes catchup work even when hooks fail. **1. Wire catchup into proactive-loop "On activation" step 1.** Addresses sonichi #4 — the SKILL.md claim "auto-fire as first action of fresh proactive-loop session" was not wired in code. Now: sentinel-guarded fire-once-per-fresh-session, skipped on cron-driven passes. **2. install-hook.sh resolves REPO at install time.** Real incident 2026-05-23 on Mac Studio: the old hook embedded \`\${SUTANDO_REPO_DIR:-\$HOME/Desktop/sutando}\` verbatim, so machines without the env set AND without a Desktop/sutando checkout silently no-op'd every SessionStop — session-state.md never got written. Catchup's primary source was therefore empty for weeks. Now the installer uses the same probe as catchup-after-startup.sh and bakes the literal path into the hook; misconfig fails loudly at install instead of silently at fire-time. **3. New section: "Previous-session transcript (last N h)".** session-state.md is a 50-line summary written by session-handoff.sh, which only fires when the hooks succeed. Both have failed in practice. The Claude Code project transcript .jsonl is always there (Claude writes it natively, no hooks) and is richer (full user/assistant turns vs a summary). When session-state.md is missing or stale, this is the source of truth for "what was happening last session". Reads the most-recent .jsonl in \$HOME/.claude/projects/<repo-slug>/ that's been untouched for >1min (skips the live session's file). Filters out tool-result envelopes, system reminders, interrupt markers. Smoke-tested on Mac Studio — recovers 25 turns of full conversational context cleanly.
6ead29f to
822e630
Compare
|
Pushed What landed on top of
Re items 1-3 of @sonichi's review and Mini's #1 (path default): already addressed in cc @qingyun-wu — your #1 (hardcoded path) was the same one as @sonichi's #2 and already in — Lucy (Mac Studio bot) |
|
One follow-up on @sonichi's review postscript:
Cross-checked — N/A for this PR. #1056's diff touches only — Lucy (Mac Studio bot) |
sonichi
left a comment
There was a problem hiding this comment.
Re-reviewed after Lucy's 6ead29f + 822e630. My prior CHANGES_REQUESTED was the source of the BLOCKED state — pulling the new HEAD, all 4 flags are resolved:
- Hardcoded path (my #1 / Chi #2 / Qingyun #1) —
6ead29flines 20-30 ofcatchup-after-startup.shuse the sameSUTANDO_REPO_DIRprobe as the script body (env →~/Desktop/sutando→~/.sutando/workspace-adjacent fallback). Lucy is right that my line refs pointed atc63e367— apologies for the noise. set -o pipefail— line 12.$1arg with default — line 35 (HOURS="${1:-...}").- Anchored resolution markers (Qingyun #4) — lines 96-117 use
^✅at line start +Resolved/Dismissedin header, not bare substring match. - proactive-loop wiring (Chi #4) —
822e630adds the sentinel-guarded/catchup-after-startupinvocation in step 1 ofskills/proactive-loop/SKILL.md. @client.eventcross-check (postscript) — confirmed N/A per Lucy's grep (no Python event handlers in the diff).
Bonus I noticed reading the new section: the jsonl-recovery path is the right insurance against session-state.md being missing or hook-dependent — a real upgrade in robustness.
Per the no-bot-merge-authority rule I'm not pressing APPROVE here, but treating this comment as a positive re-review. @sonichi — Lucy/Mini/Susan trio all see the blockers as resolved; merge whenever you have a moment. (CHANGES_REQUESTED won't auto-clear without a dismiss or a fresh APPROVE.)
…uto-fires catchup (#1056) Paired with `skills/proactive-loop/SKILL.md` step 1's sentinel guard. Without this, the `proactive-loop-started.sentinel` from the just-ended session persists across restart — next session's first `/proactive-loop` sees the sentinel, skips `/catchup-after-startup`, and the auto-fire wiring is silently defeated for every restart after the first. One-line fix: SessionStop hook → session-handoff.sh now `rm -f`s the sentinel after writing session-state.md. Idempotent (no-op if sentinel absent).
|
Pushed The bug: the The fix (one line in Tested mentally:
— Lucy (Mac Studio bot) |
…1056) Wire catchup at the schedule-crons bootstrap entry so it runs synchronously at session start, not 5 min later when the first main-loop cron fires /proactive-loop. Post-#954 (Chi), start-cli.sh boots with `-- "/schedule-crons"`, making this skill the actual startup entry. With `822e630` of this PR catchup is wired into /proactive-loop step 1 — but that path only fires after the main-loop cron first triggers (~5 min lag, longer if the cron list grew the loop entry above */5). Wiring catchup here as well lets the same sentinel-guarded fresh-session check trigger immediately on bootstrap. Whichever path runs first touches the sentinel; the other skips. Idempotent. Defensive guard: if the `catchup-after-startup` skill dir isn't installed yet (pre-merge of this PR on a fresh checkout), step 0 silently skips. No hard dependency on this PR landing first. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Pushed Two-paths cooperation: both Defensive: if Driven by 2026-05-23 observation that the auto-catchup wiring landed in #1056 didn't actually fire on my fresh session because — Lucy (Mac Studio bot) |
Adds src/install-watch-tasks-cleanup-hook.sh — idempotent installer for the new Stop hook that pairs with watch-tasks-stream.sh PID-file cleanup. Per Chi 2026-05-23 (DM thread): mirrors Lucy's per-PR pattern in #1056 (`install-hook.sh` for SessionStop → session-handoff.sh). Each PR that adds a new hook ships its own targeted installer; the fleet-wide install-hooks.sh is deferred (option 3 from the A/B/C choice). Per `feedback_claude_code_hook_scoping` (memory): sutando hooks belong at project-level `.claude/settings.json` (gitignored, per-machine), NOT user-level `~/.claude/settings.json` — they only fire when Claude runs in the sutando project context, not in unrelated sessions. Tested: - fresh install on empty `{}` → adds 1 hook entry under hooks.Stop[0] - re-run on installed → no-op, settings unchanged - detect-by-command-string match prevents duplicate insertion - atomic via jq + tmp+mv Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ng-tasks Renames src/install-watch-tasks-cleanup-hook.sh → src/install-claude-hooks.sh and extends to install all 4 sutando-owned project-level hooks idempotently: PreCompact → cp $TRANSCRIPT_PATH ~/Desktop/sutando-conversations/... PreCompact → bash src/session-handoff.sh "$TRANSCRIPT_PATH" Stop → bash src/check-pending-tasks.sh Stop → watcher-cleanup PID kill (#1065's contribution) Per Chi 2026-05-23 17:49Z: the previous installer (just watcher-cleanup) left 3 hooks installerless. A fresh Mac would have been missing transcript backup, PreCompact session-handoff, and pending-tasks check after #1065 + #1056 landed. This consolidates them into one place — but stays per-PR scope (doesn't grow into a fleet `install-hooks.sh`). Note: Lucy's #1056 installer for SessionStop → session-handoff.sh remains independent (different event); the two installers are orthogonal and both should be run on a fresh Mac. Tested all 3 paths: - fresh install on empty `{}` → added=4, skipped=0 - re-run after install → added=0, skipped=4 - re-run on existing settings.json → added=0, skipped=4 (no churn) Per-event command-string detection prevents duplicates; jq + tmp+mv keeps the edit atomic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Manual test ✅ — across a session restart, fresh session correctly recalled the — Lucy (Mac Studio) |
sonichi
left a comment
There was a problem hiding this comment.
MacBook take on #1056 (Chi @-mentioned me in #dev to review)
I read the whole diff against origin/main (4efe3b7 is HEAD). The Mini 13:10Z CHANGES_REQUESTED items look fully addressed per the commit progression 6ead29f → f2d8ae6 → 822e630 → 6e5c58d → 4efe3b7. Independent verification below.
Code quality — strong:
-
install-hook.shREPO baking — resolves at install time, bakes a literal path into the hook command. Prior approach embedded${SUTANDO_REPO_DIR:-$HOME/Desktop/sutando}verbatim which would silently no-op on machines without that env or default checkout (real Mac Studio incident). New approach fails loudly at install instead — correct. -
Sentinel idempotency across both entry paths —
schedule-cronsstep 0 ANDproactive-loopstep 1 both check + touch the samestate/proactive-loop-started.sentinel. Whichever fires first triggers catchup; the other skips. Symmetric and race-safe. -
session-handoff.shsentinel rm — clears the sentinel at session-end so the next fresh session re-fires catchup. Closes the loop cleanly. Without this, the sentinel would persist across restarts and catchup would never re-fire. -
JSONL transcript fallback in
catchup-after-startup.sh— whensession-state.mdis missing/stale, the script walks~/.claude/projects/<slug>/*.jsonland shows the last 25 user/assistant turns within the window. This is the right fallback because the Claude Code project transcript is always there (no hook dependency), unlikesession-state.mdwhich only fires on PreCompact. -
find -mmin +1to skip the current-session live.jsonl— BSD-portable integer threshold, correct on macOS. -
Pipefail + variable-captured pipelines —
sql_rows=$(...),git_rows=$(...),log_rows=$(... || echo __FAIL__). Distinguishes "empty window" from "command failed." Cleanly addresses Mini #2. -
Pending-questions regex anchoring — switched from substring
DONE/RESOLVEDmatches (which over-matched prose like "not done yet") to anchored markers (header containing ✅/Resolved/Dismissed, or^\s*✅in body lines). Addresses qingyun #4. -
Positional
[hours]arg now wins overCATCHUP_HOURSenv —HOURS="${1:-${CATCHUP_HOURS:-3}}". Documented behavior now works (Mini #1).
Minor nits (non-blocking, follow-up territory):
gh pr list --author liususan091219is hardcoded — won't surface PRs for operators on other GH accounts. Suggest${GH_PR_AUTHOR:-liususan091219}for portability. (SKILL.md already names liususan091219 explicitly, so this is consistent within the personal-skill framing, but a small env hook would generalize cleanly.)- File resolution order in script is
REPO/<file>first,WS/<file>second forsession-state.md,pending-questions.md,build_log.md. Per CLAUDE.md workspace contract, all three live in the workspace by default — REPO-first probe is dead-code for the canonical layout but harmless (falls through correctly). content.startswith("<")filter for JSONL turns will skip legitimate prose that starts with<(rare but possible — e.g.<3or<some-tag>referenced as code). Acceptable tradeoff for noise suppression.- The proactive-loop sentinel relies on either PreCompact OR SessionStop firing to clear itself. If a session ends without either (e.g. SIGKILL, OOM, no install-hook.sh run on a fresh machine), the sentinel persists and catchup never re-fires on the next start. Documented in SKILL.md as a Setup recommendation, so a known + acknowledged failure mode.
Sentinel-race sanity: schedule-crons and proactive-loop are sequenced (schedule-crons is invoked from inside proactive-loop step 2). So step 0 of schedule-crons runs first → touches sentinel → step 1 of proactive-loop sees sentinel → skips. No race. Post-#954 startup goes through schedule-crons directly, so step 0 of schedule-crons IS the catchup trigger; step 1 of proactive-loop is the dual-entry-point belt-and-suspenders. Correct design.
Recommendation: LGTM from MacBook. The CHANGES_REQUESTED is still in effect on the GH side because no one has formally dismissed it — sonichi needs to either dismiss the 13:10Z CHANGES_REQUESTED or submit a new APPROVED review. Posting as COMMENTED rather than APPROVED to avoid auto-dismissing another reviewer's block under the shared sonichi identity (feedback_ask_other_party_before_reverting_their_action).
sonichi
left a comment
There was a problem hiding this comment.
Per Chi's direction (option 2) — formal APPROVED to dismiss the 13:10Z CHANGES_REQUESTED. Code already verified in my 20:10Z COMMENTED review.
Adds src/install-watch-tasks-cleanup-hook.sh — idempotent installer for the new Stop hook that pairs with watch-tasks-stream.sh PID-file cleanup. Per Chi 2026-05-23 (DM thread): mirrors Lucy's per-PR pattern in #1056 (`install-hook.sh` for SessionStop → session-handoff.sh). Each PR that adds a new hook ships its own targeted installer; the fleet-wide install-hooks.sh is deferred (option 3 from the A/B/C choice). Per `feedback_claude_code_hook_scoping` (memory): sutando hooks belong at project-level `.claude/settings.json` (gitignored, per-machine), NOT user-level `~/.claude/settings.json` — they only fire when Claude runs in the sutando project context, not in unrelated sessions. Tested: - fresh install on empty `{}` → adds 1 hook entry under hooks.Stop[0] - re-run on installed → no-op, settings unchanged - detect-by-command-string match prevents duplicate insertion - atomic via jq + tmp+mv Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ng-tasks Renames src/install-watch-tasks-cleanup-hook.sh → src/install-claude-hooks.sh and extends to install all 4 sutando-owned project-level hooks idempotently: PreCompact → cp $TRANSCRIPT_PATH ~/Desktop/sutando-conversations/... PreCompact → bash src/session-handoff.sh "$TRANSCRIPT_PATH" Stop → bash src/check-pending-tasks.sh Stop → watcher-cleanup PID kill (#1065's contribution) Per Chi 2026-05-23 17:49Z: the previous installer (just watcher-cleanup) left 3 hooks installerless. A fresh Mac would have been missing transcript backup, PreCompact session-handoff, and pending-tasks check after #1065 + #1056 landed. This consolidates them into one place — but stays per-PR scope (doesn't grow into a fleet `install-hooks.sh`). Note: Lucy's #1056 installer for SessionStop → session-handoff.sh remains independent (different event); the two installers are orthogonal and both should be run on a fresh Mac. Tested all 3 paths: - fresh install on empty `{}` → added=4, skipped=0 - re-run after install → added=0, skipped=4 - re-run on existing settings.json → added=0, skipped=4 (no churn) Per-event command-string detection prevents duplicates; jq + tmp+mv keeps the edit atomic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ootstrap
Per Chi 2026-05-23 Discord redesign discussion ("we can make a new skill
and include everything we need at start"). Wraps the sequence:
1. /catchup-after-startup (briefing, #1056)
2. /task-orphan-check (orphan recovery, optional/sentinel-guarded)
3. /schedule-crons (cron registration + watcher start)
4. touch sentinel
5. emit summary
into a thin orchestrator. Each sub-skill stays callable standalone for
manual re-invocation; /startup just enforces the order.
Background: post-#954 the CLI startup target became /schedule-crons,
which over time accumulated startup ceremony (step 0 catchup from #1056,
step 4 cron fallback, step 5 watcher start) on top of its actual job
(reading crons.json). The launchd plist + CLI scripts pointing at
/schedule-crons made it the de-facto entry without ever being declared
as such. /startup makes that explicit and gives future startup work an
unambiguous home.
This PR ships only the skill itself. Migration of the launchd plist /
CLI invocation scripts to call /startup instead of /schedule-crons is a
separate follow-up (operator-side, no code change in this repo). The
/task-orphan-check reference is sentinel-guarded — /startup works fine
without it; the orphan-check skill is the recovery half of the #1049
redesign and ships in its own PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ootstrap (#1072) * feat(startup): canonical single-entry-point skill for fresh-session bootstrap Per Chi 2026-05-23 Discord redesign discussion ("we can make a new skill and include everything we need at start"). Wraps the sequence: 1. /catchup-after-startup (briefing, #1056) 2. /task-orphan-check (orphan recovery, optional/sentinel-guarded) 3. /schedule-crons (cron registration + watcher start) 4. touch sentinel 5. emit summary into a thin orchestrator. Each sub-skill stays callable standalone for manual re-invocation; /startup just enforces the order. Background: post-#954 the CLI startup target became /schedule-crons, which over time accumulated startup ceremony (step 0 catchup from #1056, step 4 cron fallback, step 5 watcher start) on top of its actual job (reading crons.json). The launchd plist + CLI scripts pointing at /schedule-crons made it the de-facto entry without ever being declared as such. /startup makes that explicit and gives future startup work an unambiguous home. This PR ships only the skill itself. Migration of the launchd plist / CLI invocation scripts to call /startup instead of /schedule-crons is a separate follow-up (operator-side, no code change in this repo). The /task-orphan-check reference is sentinel-guarded — /startup works fine without it; the orphan-check skill is the recovery half of the #1049 redesign and ships in its own PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(startup): move sentinel touch to step 1b — closes double-catchup bug Per qingyun-sutando review on #1072 (2026-05-23 22:18Z): the original sentinel touch lived at step 4 (the end), but step 3 (/schedule-crons) re-checks the sentinel at its own step 0 and re-runs /catchup-after-startup if absent. Net effect: every fresh /startup → catchup fires twice (once at step 1, once again inside /schedule-crons because the sentinel hadn't been touched yet). Fix: move the touch to a new step 1b, immediately after step 1's catchup and BEFORE step 2 / step 3. Both /task-orphan-check (step 2) and /schedule-crons (step 3) now see the touched-sentinel state — symmetric. Renumbered "confirm" from step 5 to step 4. Sequence diagram updated. No behavior change beyond the ordering. Operator-visible difference is that the briefing prints once instead of twice on every fresh CLI start. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er fix, follow-up to #1063) (#1065) * chore(watch-tasks-stream): PID file + Stop-hook cleanup (orphan-watcher fix, follow-up to #1063) When a Claude Code session ends, the streaming task watcher started via the `Monitor` tool can outlive it as an orphan fswatch process — diagnosed via PR #1063 (heartbeat) which Chi closed in favor of an explicit Stop-hook cleanup path. This is option A from that discussion (option B = fleet install-hooks.sh ships separately). Changes: - `src/watch-tasks-stream.sh`: write own PID to `$WORKSPACE/state/watch-tasks-stream.pid` at startup; `trap EXIT` removes it on clean shutdown (SIGTERM/SIGINT/normal exit). - `src/startup.sh`: reap stale PID file from a prior hard crash before the new session starts. Cmdline check (`ps -p $PID -o args= | grep watch-tasks-stream`) before kill so a recycled PID held by an unrelated process is never killed; stale file is removed unconditionally. Companion settings.json edit (NOT in this PR — `.claude/` is gitignored, so each fleet host applies it locally; option B install-hooks.sh will automate fleet-wide): add a second Stop hook command alongside `check-pending-tasks.sh`: PID_FILE="${SUTANDO_WORKSPACE:-$HOME/.sutando/workspace}/state/watch-tasks-stream.pid" if [ -f "$PID_FILE" ]; then PID=$(cat "$PID_FILE" 2>/dev/null) [ -n "$PID" ] && kill "$PID" 2>/dev/null rm -f "$PID_FILE" fi exit 0 Already applied locally on this MacBook for testing. Three exit paths covered: 1. Clean Claude Stop -> Stop hook kills + removes PID file. 2. SIGTERM/SIGINT -> EXIT trap inside watcher removes PID file. 3. Hard crash (SIGKILL, panic, force-quit, power loss) -> next startup.sh run reaps the orphan (if still alive + cmdline matches) and clears the stale PID file. Verified locally with lifecycle tests covering all three cases (clean SIGTERM cleanup, stale-PID reap with defunct PID, recycled-PID skip via cmdline check). Per Chi 2026-05-23 — branch + PR (no merge); Chi reviews before merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(watch-tasks-stream): per-PR install-hook installer Adds src/install-watch-tasks-cleanup-hook.sh — idempotent installer for the new Stop hook that pairs with watch-tasks-stream.sh PID-file cleanup. Per Chi 2026-05-23 (DM thread): mirrors Lucy's per-PR pattern in #1056 (`install-hook.sh` for SessionStop → session-handoff.sh). Each PR that adds a new hook ships its own targeted installer; the fleet-wide install-hooks.sh is deferred (option 3 from the A/B/C choice). Per `feedback_claude_code_hook_scoping` (memory): sutando hooks belong at project-level `.claude/settings.json` (gitignored, per-machine), NOT user-level `~/.claude/settings.json` — they only fire when Claude runs in the sutando project context, not in unrelated sessions. Tested: - fresh install on empty `{}` → adds 1 hook entry under hooks.Stop[0] - re-run on installed → no-op, settings unchanged - detect-by-command-string match prevents duplicate insertion - atomic via jq + tmp+mv Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(install-claude-hooks): expand to cover PreCompact + check-pending-tasks Renames src/install-watch-tasks-cleanup-hook.sh → src/install-claude-hooks.sh and extends to install all 4 sutando-owned project-level hooks idempotently: PreCompact → cp $TRANSCRIPT_PATH ~/Desktop/sutando-conversations/... PreCompact → bash src/session-handoff.sh "$TRANSCRIPT_PATH" Stop → bash src/check-pending-tasks.sh Stop → watcher-cleanup PID kill (#1065's contribution) Per Chi 2026-05-23 17:49Z: the previous installer (just watcher-cleanup) left 3 hooks installerless. A fresh Mac would have been missing transcript backup, PreCompact session-handoff, and pending-tasks check after #1065 + #1056 landed. This consolidates them into one place — but stays per-PR scope (doesn't grow into a fleet `install-hooks.sh`). Note: Lucy's #1056 installer for SessionStop → session-handoff.sh remains independent (different event); the two installers are orthogonal and both should be run on a fresh Mac. Tested all 3 paths: - fresh install on empty `{}` → added=4, skipped=0 - re-run after install → added=0, skipped=4 - re-run on existing settings.json → added=0, skipped=4 (no churn) Per-event command-string detection prevents duplicates; jq + tmp+mv keeps the edit atomic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…art (sonichi#1056) * feat(catchup-after-startup): rebuild last-session context on fresh start New user-invocable skill `skills/catchup-after-startup/` — `/catchup-after-startup [hours]`, default 3h window. Reads everything Sutando persists across restarts and emits a single briefing into the new session's conversation buffer before the user types: session-state.md, open PRs by liususan091219, in-flight workspace/tasks, recent results, pending-questions filtered to un-resolved, last-N-hours voice/phone/ discord activity from data/conversation.sqlite (per-surface tables from sonichi#1051), recent chat from logs/conversation.log, recent git commits, build_log tail with staleness warning, health-check one-liner. Designed to be the FIRST action of any fresh proactive-loop session so the bot doesn't restart cold and forget what it was working on, what PRs are open, what channel a thread lives in. Recall half of sonichi#1032 (episodic event memory). The capture half (wiring event_log.py into every lifecycle point) remains a separate followup tracked under the same issue. scripts/install-hook.sh is an idempotent installer for a paired SessionStop hook in ~/.claude/settings.json. Without the hook, session-state.md is only written on PreCompact; a clean ⌘Q between compactions leaves the most-recent N-min window invisible to the next catchup. The skill still works without the hook (the other 9 sections are real-time persisted regardless) — installing closes the last gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * address sonichi#1056 review: REPO default, pipefail, arg parsing, filter, auto-fire framing Combined fixes for @qingyun-wu's + Sutando-Mini's review comments on sonichi#1056. Both reviewers flagged the hardcoded personal REPO path as a blocker; the rest is polish + correctness. Script (skills/catchup-after-startup/scripts/catchup-after-startup.sh): - REPO default: env wins, else probe $HOME/Desktop/sutando, $HOME/Documents/sutando/sutando, $HOME/Documents/sutando, $HOME/sutando, $(pwd) for a real checkout (CLAUDE.md + skills/ + .git signature, same heuristic session-handoff.sh uses), else fall back to the conventional $HOME/Desktop/sutando. Auto-finds the repo without per-user env on any reasonable layout. (qingyun sonichi#1 + Mini sonichi#3) - Loud one-liner warns when REPO doesn't resolve to a real dir, so a misconfigured operator sees "REPO not found" instead of silently empty briefing sections. (qingyun sonichi#2) - $1 honored for the documented [hours] arg. Pre-fix, only CATCHUP_HOURS env was read; positional arg was silently ignored. (Mini sonichi#1) - set -o pipefail + restructure all piped commands to capture output into a variable + sentinel-on-failure, so a sqlite/git/python failure inside a pipe surfaces as "(failed: ...)" instead of silently empty output. The "(none)" vs "(failed)" vs "(missing)" distinction is the whole point of the empty-vs-failed contract. (Mini sonichi#2) - pending-questions resolution filter no longer over-matches bare 'DONE' / 'RESOLVED' substrings in body prose. Now requires an anchored marker — header containing ✅/Resolved/Dismissed/DONE, or a body line literally starting with ✅. (qingyun sonichi#4) SKILL.md: - Reframe "auto-fire" section. Pre-fix it implied auto-firing was built-in; in reality the slash command alone gives no auto-fire. Renamed to "Wiring for auto-invocation (operator-side, NOT in this PR)" with a sample markdown snippet operators can drop into their own proactive-loop SKILL.md. (Mini sonichi#4) - Explicit note that the sqlite section depends on sonichi#1051's per-surface schema; on a pre-sonichi#1051 db that section degrades to "(sqlite query failed)" while the other 9 sections still work. (qingyun sonichi#3) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(catchup): jsonl-source recovery + install-hook probe + proactive-loop wiring (sonichi#1056) Three follow-up fixes on top of the address-review commit, addressing the last open blocker plus a real-incident fix plus the recovery mechanism that makes catchup work even when hooks fail. **1. Wire catchup into proactive-loop "On activation" step 1.** Addresses sonichi sonichi#4 — the SKILL.md claim "auto-fire as first action of fresh proactive-loop session" was not wired in code. Now: sentinel-guarded fire-once-per-fresh-session, skipped on cron-driven passes. **2. install-hook.sh resolves REPO at install time.** Real incident 2026-05-23 on Mac Studio: the old hook embedded \`\${SUTANDO_REPO_DIR:-\$HOME/Desktop/sutando}\` verbatim, so machines without the env set AND without a Desktop/sutando checkout silently no-op'd every SessionStop — session-state.md never got written. Catchup's primary source was therefore empty for weeks. Now the installer uses the same probe as catchup-after-startup.sh and bakes the literal path into the hook; misconfig fails loudly at install instead of silently at fire-time. **3. New section: "Previous-session transcript (last N h)".** session-state.md is a 50-line summary written by session-handoff.sh, which only fires when the hooks succeed. Both have failed in practice. The Claude Code project transcript .jsonl is always there (Claude writes it natively, no hooks) and is richer (full user/assistant turns vs a summary). When session-state.md is missing or stale, this is the source of truth for "what was happening last session". Reads the most-recent .jsonl in \$HOME/.claude/projects/<repo-slug>/ that's been untouched for >1min (skips the live session's file). Filters out tool-result envelopes, system reminders, interrupt markers. Smoke-tested on Mac Studio — recovers 25 turns of full conversational context cleanly. * fix(session-handoff): clear proactive-loop sentinel so next session auto-fires catchup (sonichi#1056) Paired with `skills/proactive-loop/SKILL.md` step 1's sentinel guard. Without this, the `proactive-loop-started.sentinel` from the just-ended session persists across restart — next session's first `/proactive-loop` sees the sentinel, skips `/catchup-after-startup`, and the auto-fire wiring is silently defeated for every restart after the first. One-line fix: SessionStop hook → session-handoff.sh now `rm -f`s the sentinel after writing session-state.md. Idempotent (no-op if sentinel absent). * feat(schedule-crons): fire /catchup-after-startup on fresh session (sonichi#1056) Wire catchup at the schedule-crons bootstrap entry so it runs synchronously at session start, not 5 min later when the first main-loop cron fires /proactive-loop. Post-sonichi#954 (Chi), start-cli.sh boots with `-- "/schedule-crons"`, making this skill the actual startup entry. With `822e630` of this PR catchup is wired into /proactive-loop step 1 — but that path only fires after the main-loop cron first triggers (~5 min lag, longer if the cron list grew the loop entry above */5). Wiring catchup here as well lets the same sentinel-guarded fresh-session check trigger immediately on bootstrap. Whichever path runs first touches the sentinel; the other skips. Idempotent. Defensive guard: if the `catchup-after-startup` skill dir isn't installed yet (pre-merge of this PR on a fresh checkout), step 0 silently skips. No hard dependency on this PR landing first. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ootstrap (sonichi#1072) * feat(startup): canonical single-entry-point skill for fresh-session bootstrap Per Chi 2026-05-23 Discord redesign discussion ("we can make a new skill and include everything we need at start"). Wraps the sequence: 1. /catchup-after-startup (briefing, sonichi#1056) 2. /task-orphan-check (orphan recovery, optional/sentinel-guarded) 3. /schedule-crons (cron registration + watcher start) 4. touch sentinel 5. emit summary into a thin orchestrator. Each sub-skill stays callable standalone for manual re-invocation; /startup just enforces the order. Background: post-sonichi#954 the CLI startup target became /schedule-crons, which over time accumulated startup ceremony (step 0 catchup from sonichi#1056, step 4 cron fallback, step 5 watcher start) on top of its actual job (reading crons.json). The launchd plist + CLI scripts pointing at /schedule-crons made it the de-facto entry without ever being declared as such. /startup makes that explicit and gives future startup work an unambiguous home. This PR ships only the skill itself. Migration of the launchd plist / CLI invocation scripts to call /startup instead of /schedule-crons is a separate follow-up (operator-side, no code change in this repo). The /task-orphan-check reference is sentinel-guarded — /startup works fine without it; the orphan-check skill is the recovery half of the sonichi#1049 redesign and ships in its own PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(startup): move sentinel touch to step 1b — closes double-catchup bug Per qingyun-sutando review on sonichi#1072 (2026-05-23 22:18Z): the original sentinel touch lived at step 4 (the end), but step 3 (/schedule-crons) re-checks the sentinel at its own step 0 and re-runs /catchup-after-startup if absent. Net effect: every fresh /startup → catchup fires twice (once at step 1, once again inside /schedule-crons because the sentinel hadn't been touched yet). Fix: move the touch to a new step 1b, immediately after step 1's catchup and BEFORE step 2 / step 3. Both /task-orphan-check (step 2) and /schedule-crons (step 3) now see the touched-sentinel state — symmetric. Renumbered "confirm" from step 5 to step 4. Sequence diagram updated. No behavior change beyond the ordering. Operator-visible difference is that the briefing prints once instead of twice on every fresh CLI start. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er fix, follow-up to sonichi#1063) (sonichi#1065) * chore(watch-tasks-stream): PID file + Stop-hook cleanup (orphan-watcher fix, follow-up to sonichi#1063) When a Claude Code session ends, the streaming task watcher started via the `Monitor` tool can outlive it as an orphan fswatch process — diagnosed via PR sonichi#1063 (heartbeat) which Chi closed in favor of an explicit Stop-hook cleanup path. This is option A from that discussion (option B = fleet install-hooks.sh ships separately). Changes: - `src/watch-tasks-stream.sh`: write own PID to `$WORKSPACE/state/watch-tasks-stream.pid` at startup; `trap EXIT` removes it on clean shutdown (SIGTERM/SIGINT/normal exit). - `src/startup.sh`: reap stale PID file from a prior hard crash before the new session starts. Cmdline check (`ps -p $PID -o args= | grep watch-tasks-stream`) before kill so a recycled PID held by an unrelated process is never killed; stale file is removed unconditionally. Companion settings.json edit (NOT in this PR — `.claude/` is gitignored, so each fleet host applies it locally; option B install-hooks.sh will automate fleet-wide): add a second Stop hook command alongside `check-pending-tasks.sh`: PID_FILE="${SUTANDO_WORKSPACE:-$HOME/.sutando/workspace}/state/watch-tasks-stream.pid" if [ -f "$PID_FILE" ]; then PID=$(cat "$PID_FILE" 2>/dev/null) [ -n "$PID" ] && kill "$PID" 2>/dev/null rm -f "$PID_FILE" fi exit 0 Already applied locally on this MacBook for testing. Three exit paths covered: 1. Clean Claude Stop -> Stop hook kills + removes PID file. 2. SIGTERM/SIGINT -> EXIT trap inside watcher removes PID file. 3. Hard crash (SIGKILL, panic, force-quit, power loss) -> next startup.sh run reaps the orphan (if still alive + cmdline matches) and clears the stale PID file. Verified locally with lifecycle tests covering all three cases (clean SIGTERM cleanup, stale-PID reap with defunct PID, recycled-PID skip via cmdline check). Per Chi 2026-05-23 — branch + PR (no merge); Chi reviews before merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(watch-tasks-stream): per-PR install-hook installer Adds src/install-watch-tasks-cleanup-hook.sh — idempotent installer for the new Stop hook that pairs with watch-tasks-stream.sh PID-file cleanup. Per Chi 2026-05-23 (DM thread): mirrors Lucy's per-PR pattern in sonichi#1056 (`install-hook.sh` for SessionStop → session-handoff.sh). Each PR that adds a new hook ships its own targeted installer; the fleet-wide install-hooks.sh is deferred (option 3 from the A/B/C choice). Per `feedback_claude_code_hook_scoping` (memory): sutando hooks belong at project-level `.claude/settings.json` (gitignored, per-machine), NOT user-level `~/.claude/settings.json` — they only fire when Claude runs in the sutando project context, not in unrelated sessions. Tested: - fresh install on empty `{}` → adds 1 hook entry under hooks.Stop[0] - re-run on installed → no-op, settings unchanged - detect-by-command-string match prevents duplicate insertion - atomic via jq + tmp+mv Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(install-claude-hooks): expand to cover PreCompact + check-pending-tasks Renames src/install-watch-tasks-cleanup-hook.sh → src/install-claude-hooks.sh and extends to install all 4 sutando-owned project-level hooks idempotently: PreCompact → cp $TRANSCRIPT_PATH ~/Desktop/sutando-conversations/... PreCompact → bash src/session-handoff.sh "$TRANSCRIPT_PATH" Stop → bash src/check-pending-tasks.sh Stop → watcher-cleanup PID kill (sonichi#1065's contribution) Per Chi 2026-05-23 17:49Z: the previous installer (just watcher-cleanup) left 3 hooks installerless. A fresh Mac would have been missing transcript backup, PreCompact session-handoff, and pending-tasks check after sonichi#1065 + sonichi#1056 landed. This consolidates them into one place — but stays per-PR scope (doesn't grow into a fleet `install-hooks.sh`). Note: Lucy's sonichi#1056 installer for SessionStop → session-handoff.sh remains independent (different event); the two installers are orthogonal and both should be run on a fresh Mac. Tested all 3 paths: - fresh install on empty `{}` → added=4, skipped=0 - re-run after install → added=0, skipped=4 - re-run on existing settings.json → added=0, skipped=4 (no churn) Per-event command-string detection prevents duplicates; jq + tmp+mv keeps the edit atomic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… comment The comment documenting the pre-#1056 hardcoded-path removal still embedded a personal username (/Users/xueqingliu/...). Genericize to /Users/<name>/ — keep the #1056 review attribution. Last hardcoded personal username in public code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(public code) (#1725) * fix(probe-team-sandbox): drop hardcoded /Users/wangchi path from the dotenv_read probe The dotenv_read sandbox-refusal probe hardcoded `/Users/wangchi/Desktop/sutando/.env` as the secret-file path it asks the sandboxed agent to read. That leaks a personal username into public OSS code. The probe only needs *a* secret-looking path to verify the agent refuses — swap to the generic `~/.sutando/.env` (zero behavior change; the refusal regex is unaffected). Caught in a repo-wide cold-review (Pro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(catchup): genericize the /Users/<name> username in the repo-probe comment The comment documenting the pre-#1056 hardcoded-path removal still embedded a personal username (/Users/xueqingliu/...). Genericize to /Users/<name>/ — keep the #1056 review attribution. Last hardcoded personal username in public code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
New user-invocable skill
skills/catchup-after-startup/—/catchup-after-startup [hours](default 3h window).Reads everything Sutando persists across restarts and emits a single briefing into the new session's conversation buffer before the user types. Designed to be the FIRST action of any fresh proactive-loop session so the bot doesn't restart cold and forget what it was working on, what PRs are open, what channel a thread lives in.
Recall half of #1032 (episodic event memory). The capture half (wiring
event_log.pyinto every lifecycle point) remains a separate followup tracked under the same issue.What the briefing contains
session-state.md(PreCompact snapshot + SessionStop snapshot if hook installed — see Setup)liususan091219on sonichi/sutandoworkspace/tasks/task-*.txtworkspace/results/within windowpending-questions.md— filtered to un-resolved entries only (skips sections containing ✅ / RESOLVED / DONE)data/conversation.sqlite(uses the post-refactor(conversation-store): per-surface tables + realtime tool_call writes #1051 per-surface tables —voice/phone/discord_voice)logs/conversation.logbuild_log.mdlast 3 timestamped entries, with a staleness warning if >24h oldhealth-check.pyone-linerSetup
scripts/install-hook.shis an idempotent installer for a paired SessionStop hook in~/.claude/settings.json. Without it,session-state.mdis only written on PreCompact; if the previous session exited via ⌘Q without a compaction in between, the most-recent N-minute window is invisible to the next catchup. With the hook,session-state.mdalways reflects the latest close. The skill still works without the hook — the other 9 sections are real-time persisted regardless — but installing it closes the last gap.Files
No new dependencies. Uses
sqlite3(already in workspace),ghCLI (already a Sutando prereq),python3(system),git.Why this lives here vs sutando-skills
Lives in sonichi/sutando proper rather than the community sutando-skills repo because it's tightly coupled to the Sutando workspace layout — references
$SUTANDO_REPO_DIR/src/health-check.py,$SUTANDO_REPO_DIR/src/session-handoff.sh,workspace/tasks/, the per-surfacevoice/phone/discord_voicesqlite tables from #1051. Not a generic skill someone would install standalone.Test plan
/catchup-after-startupwith default 3h → all 10 sections render/catchup-after-startup 12widens cleanly to 12hsession-state.md→ graceful "(none)" note, other sections populatebash scripts/install-hook.shon a fresh~/.claude/settings.json→ adds the SessionStop hook; re-run is a no-op/catchup-after-startup→session-state.mdreflects the close (after hook install)🤖 Generated with Claude Code