fix(session-handoff): drain relay notes into session-state.md (closes #1738)#2016
fix(session-handoff): drain relay notes into session-state.md (closes #1738)#2016john-the-dev wants to merge 2 commits into
Conversation
…onichi#1738) PR sonichi#1737 removed catchup-after-startup — the only consumer of relay-*.md files written by /relay and proactive-loop step 7. Since then, 43 relay notes have accumulated in workspace/relay/ unread, breaking cross-session continuity. Wire the drain into session-handoff.sh (the PreCompact hook) so it fires on every context compaction: reads all unprocessed workspace/relay/relay-*.md in sort order, appends their content as a "## Relay Notes (from prior sessions)" section in session-state.md, then archives each to relay/processed/. The next session reads session-state.md at startup (per CLAUDE.md) and picks up the relay content naturally. Mirrors the original drain pattern from catchup-after-startup (mv to processed/ after read) without requiring a separate skill or cron entry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RUA5TZgDa7VmFcNtPka38t
Coverage Gate✅ No Python changes — nothing to gate (bar: 95% on changed lines). |
| echo "" | ||
| echo "### $(basename "$relay_file")" | ||
| cat "$relay_file" | ||
| mv "$relay_file" "$RELAY_PROCESSED/" 2>/dev/null |
There was a problem hiding this comment.
Automated review: this drains workspace/relay/*.md into processed/ while the session-state file is still being streamed directly to $STATE_FILE. If the script is interrupted or the write fails after mv "$relay_file" "$RELAY_PROCESSED/", the relay note has been retired but never made it durably into session-state.md, so the next session loses that cross-session context entirely. I think the notes need to stay in place until the new state file has been written successfully (e.g. write to a temp file first, then move/retire the relay notes after the final replace).
|
Automated review: src/session-handoff.sh:136-138 will fail on any workspace that does not already have a |
|
Automated mergeability review (2026-07-09) Signal: NOT MERGEABLE Current blocker(s):
This is a conservative backlog triage signal. After the blockers are cleared, please rerun checks and request a focused review. |
Previously each relay note was mv'd to relay/processed/ inside the loop that cat's it into the redirected block, so an interrupt after the move but before session-state.md was durably written would retire a note that was never captured — the next session loses that context. Reorder to atomic capture-then-retire: the loop now only appends note content to the redirected block; the mv to processed/ happens after the `} > "$STATE_FILE"` write completes, gated on the state file being non-empty AND each note's header actually being present in it. If the capture failed or was interrupted, the note is left in place to retry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DFp2p6XUSAQ1eAKKZeiZPt
|
@cla-assistant check |
qingyun-wu
left a comment
There was a problem hiding this comment.
Delta review of 9c834f2e (since my 2026-07-08 15:06 review). CI: all checks green.
Prior point — addressed:
- Retire-before-durable-write hazard — fixed. The capture loop no longer moves notes while streaming into
$STATE_FILE; retirement happens only after the brace-group write completes, gated on[ -s "$STATE_FILE" ]plus a per-notegrep -qF "### $(basename ...)"confirmation that the note's header actually landed (src/session-handoff.sh:161-167). An interrupt between capture and retire now leaves the notes in place for the next run instead of losing them — exactly the at-least-once behavior I asked for. The brace group runs in the current shell, sounprocessed_relay/RELAY_PROCESSEDare correctly visible to the retire block; I also ran the capture+retire flow locally (2 notes drained toprocessed/, content present in the state file; re-run withrelay/absent exits 0 with a clean state file).
Closing the loop on the 2026-07-08 22:46 automated follow-up comment about set -euo pipefail aborting on a missing relay/ dir: that concern does not apply — this script deliberately does not use set -e (see the comment at src/session-handoff.sh:28), find's stderr is suppressed, and the pipeline's exit status is sort's. Verified empirically: missing relay/ yields an empty list and the section is skipped cleanly.
Non-blocking: if the state-file write is partially truncated by e.g. ENOSPC mid-stream, a note whose ### header made it in before truncation could still be retired with a clipped body. The temp-file+rename approach from the original comment would close that residual window too, but given the header check and that session-state.md is regenerated every compaction, this is an acceptable edge.
LGTM (comment only). Branch is behind main (merge state BEHIND) — needs an update-branch before merge; no conflicts reported.
Problem
PR #1737 removed
catchup-after-startup— the only consumer ofrelay-*.mdfiles written by/relayandproactive-loopstep 7. Since then, 43 relay notes have accumulated inworkspace/relay/unread, breaking cross-session continuity (#1738).Fix
Wire the drain into
session-handoff.sh(the PreCompact hook), which already runs on every context compaction and writessession-state.md:workspace/relay/relay-*.mdfiles in sort order.## Relay Notes (from prior sessions)insession-state.md.relay/processed/(mirrors the original drain pattern fromcatchup-after-startup).The next session reads
session-state.mdat startup (per CLAUDE.md "Session Continuity" section) and gets the relay content naturally — no new skill, no new cron, no new config.Why session-handoff.sh
session-handoff.shalready hasWORKSPACE_DIRresolved, runs exactly when session memory rolls off (context compaction), and produces the file the next session reads. It's the minimal, already-running hook that can do this drain without adding a new mechanism.Test
Ran locally with 43 accumulated relay notes: all 43 moved to
relay/processed/,session-state.mdincludes the## Relay Notes (from prior sessions)section, script exits 0.🤖 Generated with Claude Code
https://claude.ai/code/session_01RUA5TZgDa7VmFcNtPka38t