Skip to content

fix(resume-brief): exclude live sessions, show session id, stay silent when clean#227

Open
khustup2 wants to merge 9 commits into
mainfrom
fix/resume-brief-exclude-live-sessions
Open

fix(resume-brief): exclude live sessions, show session id, stay silent when clean#227
khustup2 wants to merge 9 commits into
mainfrom
fix/resume-brief-exclude-live-sessions

Conversation

@khustup2
Copy link
Copy Markdown
Contributor

@khustup2 khustup2 commented Jun 2, 2026

Summary

Four fixes to the SessionStart pickup banner so it only surfaces genuine open work and never points at a session you're still in.

  1. Live sessions excluded by process liveness — replaces the flawed event-heartbeat window with a /proc walk (pid + comm + starttime) recorded at SessionStart. An idle-but-open session keeps its process, so it stays excluded; a crashed session (dead process) becomes eligible immediately; already-open sessions self-heal on their next event.
  2. Session id shown↳ session <uuid> in the banner, copy-pasteable for claude --resume <id>.
  3. "None — …" no longer faked as work — completion phrasings (None — feature complete, N/A, all shipped, Nothing pending.) count as clean; a real None of the tests pass still surfaces.
  4. Silent when nothing pending — no banner at all unless a recent session has genuine open work. Removes the old "wrapped up clean" line and the reach-back to stale TODOs.

Caveats

  • Linux-only liveness (/proc); other platforms degrade to the heartbeat window.
  • Local-only — a session on another machine can't be detected (would need a DB heartbeat).

Testing

Full suite: 3766 passing. Built into the bundle and verified live (sidLine present, arrow escaped to , EMPTY_SECTION updated, sid carried through selectRealSummaries).

Summary by CodeRabbit

  • New Features

    • Resume recommendations now display session IDs for easier identification
  • Bug Fixes

    • Improved session lifecycle management to properly mark completed sessions and prevent resume suggestions from active, in-progress work
    • Fixed timestamp tracking for goals and KPIs to correctly distinguish creation time from last modification time

khustup2 added 2 commits June 2, 2026 20:27
Every goal/KPI write went through an UPDATE that set created_at = now,
clobbering the original creation timestamp. Because the listing and VFS
bootstrap order by created_at DESC (and KPIs by created_at ASC), any
status move or content edit silently reset the timestamp and bumped the
row to the top — reshuffling the list on every transition.

Add a dedicated updated_at column (auto-healed onto existing tables) and
record edit time there, leaving created_at untouched. INSERT paths now
populate both. Covers all write sites: VFS upsertGoalRow/upsertKpiRow,
the goal/kpi CLI, and the openclaw write-side tools.

Adds a regression test asserting a status transition writes updated_at
and never touches created_at.
…t when clean

The SessionStart pickup banner surfaced sessions still open in another
terminal, and dressed up finished sessions ("None" next steps) as open work.

- Liveness via the owning process, not an event heartbeat. SessionStart records
  the owning `claude` process (/proc walk: pid+comm+starttime) to <sid>.owner;
  capture self-heals already-open sessions on their next event. isSessionLive:
  ended-marker -> not live; else owner alive->live / dead->not (catches crashes
  too); unknown -> fall back to the event-heartbeat window. An idle-but-open
  session keeps its process, so it stays excluded.
- resume-brief excludes the current session and any live session before
  windowing; shows the surfaced session's id (copy-pasteable for --resume).
- Treat "None - <prose>" / "N/A, ..." Next Steps as wrapped-clean (not just a
  bare "none"), without matching real work like "None of the tests pass".
- Stay silent (no banner) when no recent session has open work, instead of a
  "nothing pending" line or reaching back to a stale TODO.

Linux-only liveness (/proc); non-Linux degrades to the heartbeat window.
Known limit: local-only - a session on another machine can't be detected.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

Coverage Report

Scope: files changed in this PR. Enforced threshold: 90% per metric (per file via vitest.config.ts).

Status Category Percentage Covered / Total
🟢 Lines 96.66% (🎯 90%) 1477 / 1528
🟢 Statements 95.50% (🎯 90%) 1718 / 1799
🟢 Functions 96.52% (🎯 90%) 194 / 201
🟢 Branches 90.33% (🎯 90%) 1140 / 1262
File Coverage — 16 files changed
File Stmts Branches Functions Lines
src/commands/goal.ts 🟢 96.2% 🟢 92.5% 🟢 100.0% 🟢 98.8%
src/deeplake-schema.ts 🟢 95.9% 🔴 84.8% 🟢 100.0% 🟢 95.1%
src/hooks/capture.ts 🟢 97.5% 🔴 83.3% 🟢 100.0% 🟢 100.0%
src/hooks/codex/spawn-wiki-worker.ts 🟢 95.0% 🟢 100.0% 🔴 66.7% 🟢 95.0%
src/hooks/cursor/spawn-wiki-worker.ts 🟢 100.0% 🔴 66.7% 🟢 100.0% 🟢 100.0%
src/hooks/hermes/spawn-wiki-worker.ts 🟢 100.0% 🔴 75.0% 🟢 100.0% 🟢 100.0%
src/hooks/pre-tool-use.ts 🟢 95.1% 🔴 88.6% 🟢 94.1% 🟢 94.9%
src/hooks/session-end.ts 🟢 97.7% 🟢 90.9% 🟢 100.0% 🟢 100.0%
src/hooks/session-notifications.ts 🟢 92.8% 🟢 90.0% 🔴 75.0% 🟢 100.0%
src/hooks/session-start.ts 🟢 100.0% 🟢 96.3% 🟢 100.0% 🟢 100.0%
src/hooks/spawn-wiki-worker.ts 🟢 100.0% 🟢 100.0% 🟢 100.0% 🟢 100.0%
src/hooks/summary-state.ts 🔴 83.8% 🔴 87.2% 🟢 96.4% 🔴 85.4%
src/notifications/index.ts 🟢 100.0% 🟢 95.0% 🟢 100.0% 🟢 100.0%
src/notifications/sources/primary-banner.ts 🔴 89.1% 🔴 88.5% 🟢 92.3% 🔴 89.0%
src/notifications/sources/resume-brief.ts 🟢 100.0% 🟢 96.0% 🟢 100.0% 🟢 100.0%
src/shell/deeplake-fs.ts 🟢 97.0% 🟢 91.4% 🟢 97.0% 🟢 99.1%

Generated for commit 65a40d7.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 43f274a7-ad22-4892-a594-6d1ea8e75e6a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements session-aware timestamp tracking and liveness detection. Goals and KPIs tables gain updated_at columns to track modification time separately from creation time. A new session state infrastructure detects live sessions via process ownership and file-based heartbeats. Session lifecycle hooks integrate this to mark session start, end, and capture events. Resume briefs now filter out summaries from active sessions, preventing stale cross-terminal suggestions.

Changes

Session Liveness and Timestamp Lifecycle

Layer / File(s) Summary
Schema and timestamp management
src/deeplake-schema.ts, src/commands/goal.ts, src/shell/deeplake-fs.ts, openclaw/src/index.ts, tests/claude-code/cli-goal.test.ts, tests/openclaw/hivemind-tools.test.ts, tests/claude-code/deeplake-fs.test.ts
Goals and KPIs schema definitions now include updated_at columns. INSERT statements across CLI, shell, and tool layers populate both created_at and updated_at on row creation. UPDATE statements for status and content changes now set updated_at while preserving created_at. Tests validate the new column presence and correct SQL ordering.
Session state infrastructure
src/hooks/summary-state.ts, tests/claude-code/summary-state.test.ts
New module exports a session liveness layer: /proc/<pid>/stat parsing for process info, parent-chain walking to find owning agent process, activity-window-based heartbeat freshness, sidecar marker files for clean session termination, and isSessionLive decision logic that prefers recorded owner liveness and falls back to state-file mtime. Tests cover liveness detection across fresh/stale heartbeats, ended-marker precedence, owner process reuse via starttime, and configurable window overrides.
Session lifecycle hooks integration
src/hooks/capture.ts, src/hooks/session-start.ts, src/hooks/session-end.ts, tests/claude-code/capture-hook.test.ts, tests/claude-code/session-end-hook.test.ts
Capture hook calls ensureSessionOwner for self-healing when resuming sessions. Session-start hook records owner and clears stale ended markers. Session-end hook marks session cleanly ended before lock coordination. Tests mock state helpers and validate ownership recording and ended-marker behavior across early-exit and lock scenarios.
Resume brief filtering and session awareness
src/notifications/sources/resume-brief.ts, src/notifications/sources/primary-banner.ts, tests/claude-code/resume-brief.test.ts
Resume briefs now accept the current sessionId to filter out summaries from active sessions and the current session itself. New helpers extract session IDs from summary paths and exclude live sessions. Empty-work regex expands to treat more "None …" patterns as wrapped-clean. Briefs now include copy-pasteable session IDs for open work and return null (silent) when all recent summaries are wrapped clean. Tests control liveness offline and validate filtering, edge cases in path parsing, and brief silence when no fresh work exists.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • activeloopai/hivemind#223: Resume/cold-start session brief pipeline changes—both modify src/notifications/sources/resume-brief.ts and src/notifications/sources/primary-banner.ts for session-id-aware brief selection.
  • activeloopai/hivemind#98: Session-end hook changes—main PR adds immediate markSessionEnded handling while the retrieved PR adds forceSessionEndTrigger at the same code path.

Suggested reviewers

  • kaghni
  • efenocchi

Poem

🐰 Timestamps now track when goals were born and last caressed,
Session owners whisper through /proc which process knows them best,
Resume briefs skip the living—no stale echoes from afar,
Each session ends with grace, a marker in the sidecar,
Heartbeats fade to quiet when activity grows weary and wise.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main focus of the PR: fixing the resume-brief by excluding live sessions, displaying session IDs, and staying silent when work is clean.
Description check ✅ Passed The PR description includes a comprehensive summary of the four fixes with clear explanations, caveats about Linux-only behavior and local-only detection, and testing details; however, the Version Bump section of the template is missing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/resume-brief-exclude-live-sessions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot requested review from efenocchi and kaghni June 2, 2026 21:20
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/session-start.ts`:
- Around line 145-151: When starting/resuming a session, also update the session
activity timestamp so the non-Linux mtime fallback used by isSessionLive() /
activeWindowMs() marks the resumed session as live; specifically, in the
SessionStart block after clearSessionEnded(input.session_id) and
recordSessionOwner(input.session_id) add a call to the helper that updates the
session activity mtime (e.g. touchSessionActivity or
updateSessionActivityTimestamp) for input.session_id — if that helper does not
exist, implement it to persistently update the session’s activity timestamp in
the same state storage isSessionLive() reads.

In `@src/notifications/sources/resume-brief.ts`:
- Around line 107-112: The EMPTY_SECTION regex in resume-brief.ts currently
treats "TBD" with trailing punctuation as empty, suppressing real work in
extractNextSteps(); update the EMPTY_SECTION pattern so that only
none/n\/?a|n\.a\.|nothing(?: pending)? (and the punctuation-only dashes) keep
the relaxed "trailing-clause" group, but match "tbd" only as a standalone token
(no trailing-clause allowed). In practice, refactor the alternation so the (tbd)
branch does not share the \s*(?:[—–\-.,;:].*)? suffix while the other branches
keep it, leaving extractNextSteps() behavior unchanged for the other empty
tokens.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54444424-19ec-41c6-bca0-9d4a16118d9c

📥 Commits

Reviewing files that changed from the base of the PR and between 2bf77fb and 8392077.

📒 Files selected for processing (17)
  • openclaw/src/index.ts
  • src/commands/goal.ts
  • src/deeplake-schema.ts
  • src/hooks/capture.ts
  • src/hooks/session-end.ts
  • src/hooks/session-start.ts
  • src/hooks/summary-state.ts
  • src/notifications/sources/primary-banner.ts
  • src/notifications/sources/resume-brief.ts
  • src/shell/deeplake-fs.ts
  • tests/claude-code/capture-hook.test.ts
  • tests/claude-code/cli-goal.test.ts
  • tests/claude-code/deeplake-fs.test.ts
  • tests/claude-code/resume-brief.test.ts
  • tests/claude-code/session-end-hook.test.ts
  • tests/claude-code/summary-state.test.ts
  • tests/openclaw/hivemind-tools.test.ts

Comment on lines +145 to +151
// A fresh start or --resume of this session re-activates it: drop any stale
// ended marker and record the owning `claude` process so other sessions can
// tell this one is live even while it sits idle waiting on the user.
if (input.session_id) {
clearSessionEnded(input.session_id);
recordSessionOwner(input.session_id);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Re-arm the heartbeat fallback on SessionStart.

On non-Linux, Line 150 is a no-op, so isSessionLive() falls back to the existing state-file mtime. If the user resumes an older session and then stays idle, that mtime can already be outside activeWindowMs(), and another terminal can immediately surface this still-open session in the resume banner until the first new event arrives. Touch the session’s activity timestamp here as well so the documented non-Linux fallback actually marks resumed sessions live.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/session-start.ts` around lines 145 - 151, When starting/resuming a
session, also update the session activity timestamp so the non-Linux mtime
fallback used by isSessionLive() / activeWindowMs() marks the resumed session as
live; specifically, in the SessionStart block after
clearSessionEnded(input.session_id) and recordSessionOwner(input.session_id) add
a call to the helper that updates the session activity mtime (e.g.
touchSessionActivity or updateSessionActivityTimestamp) for input.session_id —
if that helper does not exist, implement it to persistently update the session’s
activity timestamp in the same state storage isSessionLive() reads.

Comment on lines +107 to +112
/** Treat these as "nothing left" even when the section is present. Matches the
* bare tokens ("None", "N/A", "TBD") AND a token followed by a trailing clause
* introduced by punctuation or a dash ("None — feature complete", "N/A, all
* shipped", "Nothing pending."). A token followed by a *word* ("None of the
* tests pass yet") is real open work and deliberately does NOT match. */
const EMPTY_SECTION = /^(none|n\/?a|n\.a\.|nothing(?: pending)?|tbd|—|–|-)\s*(?:[—–\-.,;:].*)?$/i;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't classify TBD: or TBD - … as wrapped-clean.

Line 112 now treats any TBD followed by punctuation as empty, so extractNextSteps() will suppress real open work like TBD: finish rollback handling or TBD - wire the migration. That makes the resume banner go silent for sessions that still have pending work. Keep the trailing-clause relaxation for none/n/a/nothing pending, but only treat TBD as empty when it stands alone.

Suggested fix
-const EMPTY_SECTION = /^(none|n\/?a|n\.a\.|nothing(?: pending)?|tbd|—|–|-)\s*(?:[—–\-.,;:].*)?$/i;
+const EMPTY_SECTION =
+  /^(?:(?:none|n\/?a|n\.a\.|nothing(?: pending)?)(?:\s*(?:[—–\-.,;:].*)?)?|tbd|—|–|-)$/i;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Treat these as "nothing left" even when the section is present. Matches the
* bare tokens ("None", "N/A", "TBD") AND a token followed by a trailing clause
* introduced by punctuation or a dash ("None — feature complete", "N/A, all
* shipped", "Nothing pending."). A token followed by a *word* ("None of the
* tests pass yet") is real open work and deliberately does NOT match. */
const EMPTY_SECTION = /^(none|n\/?a|n\.a\.|nothing(?: pending)?|tbd|||-)\s*(?:[\-.,;:].*)?$/i;
/** Treat these as "nothing left" even when the section is present. Matches the
* bare tokens ("None", "N/A", "TBD") AND a token followed by a trailing clause
* introduced by punctuation or a dash ("None — feature complete", "N/A, all
* shipped", "Nothing pending."). A token followed by a *word* ("None of the
* tests pass yet") is real open work and deliberately does NOT match. */
const EMPTY_SECTION =
/^(?:(?:none|n\/?a|n\.a\.|nothing(?: pending)?)(?:\s*(?:[\-.,;:].*)?)?|tbd|||-)$/i;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/notifications/sources/resume-brief.ts` around lines 107 - 112, The
EMPTY_SECTION regex in resume-brief.ts currently treats "TBD" with trailing
punctuation as empty, suppressing real work in extractNextSteps(); update the
EMPTY_SECTION pattern so that only none/n\/?a|n\.a\.|nothing(?: pending)? (and
the punctuation-only dashes) keep the relaxed "trailing-clause" group, but match
"tbd" only as a standalone token (no trailing-clause allowed). In practice,
refactor the alternation so the (tbd) branch does not share the
\s*(?:[—–\-.,;:].*)? suffix while the other branches keep it, leaving
extractNextSteps() behavior unchanged for the other empty tokens.

khustup2 added 7 commits June 2, 2026 21:29
- Record the session owner from PreToolUse (synchronous, reliable `claude`
  ancestor) in addition to SessionStart. The async capture hook can be
  detached and unable to /proc-walk to claude, so a session already open when
  this shipped only got an owner record late or not at all — and an
  idle-but-live session past the heartbeat window was wrongly surfaced. The
  synchronous hook records it on the session's next tool call; no-op once set.
- Surface up to MAX_BRIEF_SESSIONS (2) sessions with open work, newest-first,
  each as a `📌 <next>` + `↳ session <id> · <age>` block under one CTA. Still
  silent when nothing has open work.
…ions

When a session's core work was substantive (feature, fix, investigation,
design), the wiki summarizer now treats administrative wrap-up — commit,
push, open/merge PR, deploy, monitor CI — as ALREADY DONE and writes "none"
instead of surfacing it as the next step. So the resume brief stops echoing
procedural closure noise ("Push the branch", "Monitor PR checks") and lets
genuine forgotten work rise. Administrative actions still count when the
session's core purpose WAS the release/ops task. Applied to all four agent
prompt variants (claude / cursor / hermes / codex).
A `claude --resume` (source="resume") already puts the prior thread in front
of the user, so the welcome / "where you left off" banner is pure noise.
Thread the SessionStart `source` through session-notifications →
drainSessionStart → pickPrimaryBanner and return null when source==="resume".
Unknown/absent source is treated as a fresh startup so older Claude Code
versions that don't send the field keep the banner.
…rth resuming

Strengthens the admin-suppression rule: the summarizer no longer mechanically
emits a next step. It now defaults to "none" and only names one when the
session left genuinely substantive, non-obvious work a returning engineer would
actually need flagged — judging whether it's worth mentioning. Natural stopping
points, completed work, trivial/obvious follow-ups, open-ended exploration, and
administrative wrap-up (commit/push/PR/deploy/CI) all resolve to "none". Stops
the resume brief from surfacing procedural noise. Applied to all four agent
prompt variants (claude / cursor / hermes / codex).
The pickup query fetched 40 full summaries every SessionStart; most are
SessionStart placeholders. As history grew it climbed to ~2.6s and started
losing the race against the 3s cap — the banner silently vanished.

- Exclude placeholders in SQL (description <> 'in progress'); isPlaceholderSummary
  still guards in code for any non-standard placeholder.
- Drop SCAN_LIMIT 40 -> 20 (the big cushion only existed to skip placeholders).
- Raise the query cap 3s -> 4s and the session-notifications hook timeout 5s ->
  8s for cold-spike headroom.

Measured locally: ~2612ms -> ~550-720ms (~4x), same two real sessions surfaced.
…beat on start

- EMPTY_SECTION no longer swallows "TBD: <work>" / "TBD - <work>" as
  wrapped-clean; only a bare "TBD" counts as empty (none/n-a/nothing-pending
  keep the trailing-clause relaxation). Real pending work surfaces again.
- SessionStart now calls touchSessionActivity() so the non-Linux mtime
  fallback in isSessionLive() marks a freshly started/resumed session live
  immediately, before its first captured event (on Linux the owner record
  already covers this). Seeds empty state without touching periodic counters.
- Tests for both, plus recordSessionOwner no-match and ensureSessionOwner
  no-op-when-exists — summary-state branch coverage 82% -> 86%.
Tests-only. The PR coverage comment showed changed-files branches at 89.3%
(<90%). Add coverage for:
- session-end recordSessionUsage: transcript with/without memory searches and
  the parse-error catch (was the biggest gap at 63% branch).
- resume-brief: empty-session-id paths (excludeActiveSessions + pathless
  summaries, with and without an age line) and the default table/apiUrl/
  workspaceId fallbacks.
- summary-state ownerLiveness: dead-on-comm-mismatch.

PR-scope branch coverage now ~90.5%.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant