Skip to content

Add cross-directory session resume via global registry#192

Merged
subsy merged 8 commits intomainfrom
claude/investigate-issue-175-QkNwJ
Jan 22, 2026
Merged

Add cross-directory session resume via global registry#192
subsy merged 8 commits intomainfrom
claude/investigate-issue-175-QkNwJ

Conversation

@subsy
Copy link
Owner

@subsy subsy commented Jan 22, 2026

Summary

This PR implements a global session registry that enables resuming Ralph TUI sessions from any directory using session IDs, rather than requiring users to be in the project directory or use --cwd. Sessions are now tracked in ~/.config/ralph-tui/sessions.json and can be discovered, listed, and resumed by ID.

Key Changes

New Session Registry System (src/session/registry.ts)

  • Created SessionRegistry to maintain a global index of all sessions
  • Implemented registry operations: registerSession, updateRegistryStatus, unregisterSession, getSessionById, getSessionByCwd
  • Added session discovery: listResumableSessions, findSessionsByPrefix, cleanupStaleRegistryEntries
  • Registry persists to ~/.config/ralph-tui/sessions.json with version 1 schema

Enhanced Resume Command (src/commands/resume.tsx)

  • Added --list flag to display all resumable sessions with status, agent, and tracker info
  • Added --cleanup flag to remove stale registry entries
  • Added positional session-id argument for cross-directory resume
  • Implemented resolveSession() to intelligently find sessions by ID, prefix, or current directory
  • Added helpful error messages suggesting available sessions when none found
  • Updated help text with new usage patterns and examples

Updated Run Command (src/commands/run.tsx)

  • Sessions are now automatically registered when started via registerSession()
  • Registry status is updated on completion, failure, or pause
  • Sessions are unregistered from registry on successful completion

Documentation Updates (website/content/docs/cli/resume.mdx)

  • Added session registry explanation and benefits
  • Documented new --list and --cleanup options
  • Added examples for listing sessions and resuming by ID
  • Added session states table with icons and resumability
  • Updated troubleshooting section with registry-related issues

Testing (src/session/registry.test.ts)

  • Comprehensive test suite covering registration, lookup, listing, and cleanup
  • Tests for prefix matching, status updates, and stale entry removal
  • Tests for registry persistence and structure validation

Notable Implementation Details

  • Prefix Matching: Users can resume with just the first 8 characters of a session ID; ambiguous prefixes show helpful error messages
  • Smart Resolution: Resume command tries exact ID match → prefix match → current directory → registry suggestions
  • Automatic Cleanup: Registry entries are removed when sessions complete successfully
  • Status Tracking: Registry maintains session status (running, paused, interrupted, completed, failed) with visual icons
  • Backward Compatible: Existing --cwd flag still works; registry is additive functionality
  • Metadata Preservation: Registry tracks agent plugin, tracker plugin, epic ID, PRD path, and sandbox mode for context

User Experience Improvements

  • No need to cd into project directory to resume sessions
  • ralph-tui resume --list provides overview of all active work
  • Partial session ID matching reduces typing
  • Clear error messages guide users when sessions aren't found
  • Registry cleanup prevents stale entries from accumulating

Summary by CodeRabbit

  • New Features

    • Cross-directory resume backed by a persistent session registry; resume by session-id from any directory
    • Resume command: added --list (-l), --cleanup and positional session-id; registry-aware headless and TUI resumes
  • Bug Fixes / Reliability

    • More robust session tracking, lifecycle updates and atomic registry persistence with locking to prevent corruption
  • Documentation

    • Expanded CLI docs with registry details, session states, examples and troubleshooting
  • Tests

    • Extensive test coverage for registry and resume flows

✏️ Tip: You can customize this high-level summary in your review settings.

This commit addresses multiple issues with session resuming:

1. **Fix paste support in file path input**: The EpicLoaderOverlay now
   accepts multi-character input sequences, allowing users to paste file
   paths instead of typing them character by character.

2. **Add session registry for cross-directory resume**: Sessions are now
   registered in a global registry (~/.config/ralph-tui/sessions.json),
   enabling users to:
   - List all resumable sessions with `ralph-tui resume --list`
   - Resume sessions by ID from any directory
   - Clean up stale entries with `ralph-tui resume --cleanup`

3. **Improve error messages**: When no session is found, the command now
   shows helpful suggestions including available sessions from other
   directories.

Fixes #175
@vercel
Copy link

vercel bot commented Jan 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Review Updated (UTC)
ralph-tui Ignored Ignored Preview Jan 22, 2026 9:22pm

Request Review

@codecov
Copy link

codecov bot commented Jan 22, 2026

Codecov Report

❌ Patch coverage is 71.83771% with 118 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.85%. Comparing base (bd5762f) to head (1335362).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
src/session/registry.ts 72.87% 67 Missing ⚠️
src/commands/resume.tsx 79.47% 31 Missing ⚠️
src/commands/run.tsx 0.00% 20 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #192      +/-   ##
==========================================
- Coverage   44.91%   44.85%   -0.07%     
==========================================
  Files          76       78       +2     
  Lines       22091    22872     +781     
==========================================
+ Hits         9923    10260     +337     
- Misses      12168    12612     +444     
Files with missing lines Coverage Δ
src/session/index.ts 10.04% <100.00%> (+0.39%) ⬆️
src/commands/run.tsx 8.35% <0.00%> (-0.11%) ⬇️
src/commands/resume.tsx 30.40% <79.47%> (ø)
src/session/registry.ts 72.87% <72.87%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Add tests for:
- listAllSessions function
- getRegistryFilePath function
- cleanupStaleRegistryEntries edge case (no stale entries)

Increases function coverage to 100%.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

Warning

Rate limit exceeded

@subsy has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 28 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Adds a durable, locked filesystem-backed session registry (~/.config/ralph-tui/sessions.json) and integrates it into run/resume flows. Exposes registry APIs (register/update/unregister and queries), enables cross-directory resume via session-id with listing/cleanup flags, and adds comprehensive tests and docs.

Changes

Cohort / File(s) Summary
Session Registry Implementation
src/session/registry.ts
New durable, locked JSON registry with atomic writes and strict permissions. Defines SessionRegistryEntry/SessionRegistry and APIs: getRegistryFilePath, loadRegistry, saveRegistry, registerSession, updateRegistryStatus, unregisterSession, getSessionById, getSessionByCwd, listResumableSessions, listAllSessions, cleanupStaleRegistryEntries, findSessionsByPrefix.
Session Exports
src/session/index.ts
Re-exports registry APIs and types from the registry module so callers can use registry utilities via src/session.
Run Command Integration
src/commands/run.tsx
Calls registerSession after initial persist; calls updateRegistryStatus on mid-run saves or errors; calls unregisterSession on successful completion; persists session metadata (sessionId, cwd, plugins, flags) to registry.
Resume Command Enhancement
src/commands/resume.tsx
Adds ResumeArgs type and extended parseResumeArgs; supports positional session-id, --list/-l, and --cleanup; adds helpers formatSessionEntry, listSessions, cleanupRegistry, resolveSession; resolves sessions by id, prefix, or cwd and updates registry on pause/finish.
Registry Tests
src/session/registry.test.ts
New comprehensive tests covering register/update/unregister, lookups by id/cwd, resumable filters, prefix search, cleanup of stale entries, save/load round-trips, concurrency, and path validation.
Resume Command Tests
tests/commands/resume.test.ts
New tests for parseResumeArgs, formatSessionEntry, listSessions, cleanupRegistry, resolveSession, and help text; extensively mocks registry/session APIs and verifies console output/flows.
Docs & UI
website/content/docs/cli/resume.mdx, src/tui/components/EpicLoaderOverlay.tsx
Documentation updated with Session Registry section, session states, examples for listing/resuming/cleanup and new CLI flags. Small UI tweak: file-prompt accepts multi-character (pasted) input sequences.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant RunCmd as Run Command
    participant Registry as Session Registry
    participant ResumeCmd as Resume Command

    User->>RunCmd: start run
    RunCmd->>RunCmd: persist initial session state
    RunCmd->>Registry: registerSession(sessionId, cwd, status: running)
    Registry-->>RunCmd: ack

    alt mid-run save
        RunCmd->>Registry: updateRegistryStatus(sessionId, status)
        Registry-->>RunCmd: ack
    else error
        RunCmd->>Registry: updateRegistryStatus(sessionId, failed)
        Registry-->>RunCmd: ack
    else completion
        RunCmd->>Registry: unregisterSession(sessionId)
        Registry-->>RunCmd: ack
    end

    User->>ResumeCmd: resume --list
    ResumeCmd->>Registry: listResumableSessions()
    Registry-->>ResumeCmd: sessions list
    ResumeCmd-->>User: display sessions

    User->>ResumeCmd: resume <session-id>
    ResumeCmd->>Registry: getSessionById(session-id) or findSessionsByPrefix
    Registry-->>ResumeCmd: session entry(ies)
    ResumeCmd->>ResumeCmd: resolve session path/state
    ResumeCmd->>RunCmd: launch engine with resolved session
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Poem

🐰 I hopped through folders to mark each run,

A cosy file to keep the sessions spun.
List them, tidy, jump across with glee —
A carrot for each saved res-ume key.
(Soft hops, bright logs and a tidy tree!)

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarises the main objective of the changeset: adding global session registry support for cross-directory resume functionality.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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


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.

Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In `@src/session/registry.ts`:
- Around line 80-116: The registry directory and file are created with default
permissive Unix modes; update ensureRegistryDir to call mkdir with mode: 0o700
(and keep recursive: true) and update saveRegistry to write the file with a
restrictive mode: 0o600 when calling writeFile; additionally, after creation (in
ensureRegistryDir and saveRegistry) ensure existing items are corrected by
calling fs.chmod on REGISTRY_DIR and the path returned by getRegistryPath() to
enforce 0o700 and 0o600 respectively so permissions are correct even if the
directory/file already existed.
- Around line 113-152: The registry mutations (registerSession,
updateRegistryStatus, unregisterSession) must acquire a file-level lock around
the read-modify-write sequence to avoid concurrent clobbering; implement a lock
(advisory flock or lockfile) around loadRegistry -> modify registry.sessions ->
saveRegistry and ensure the lock is always released even on errors. Replace the
current writeFile usage in saveRegistry with an atomic-write pattern: write JSON
to a temp file in the same directory as getRegistryPath(), fsync the temp file
(and its parent dir if possible), then rename the temp file over the target path
to ensure atomic replace and durability; ensure errors during write/rename clean
up the temp file. Keep ensureRegistryDir and getRegistryPath for locating files,
and integrate locking/unlocking with these functions so all registry mutations
are serialized across processes.
🧹 Nitpick comments (3)
src/session/registry.test.ts (2)

22-24: Consider test isolation for registry tests.

The comment correctly notes that tests modify the actual registry file. This could cause issues if tests run in parallel or if the developer has real sessions. Consider using dependency injection or a test-specific registry path for better isolation.


276-286: Weak assertion may cause flaky tests.

The assertion expect(cleaned).toBeGreaterThanOrEqual(1) is weak because other sessions from concurrent tests or previous runs might exist. Since you register sessionId2 and mock it as non-existent, you should be able to assert exactly what was cleaned.

🔧 Suggested fix
-      expect(cleaned).toBeGreaterThanOrEqual(1);
+      // We registered sessionId2 and mocked /tmp/gone as non-existent
+      // so at minimum 1 entry should be cleaned
+      expect(cleaned).toBeGreaterThanOrEqual(1);
+      // Verify sessionId2 was specifically removed

The current assertion is acceptable given the shared test environment, but documenting the rationale would improve clarity.

src/commands/resume.tsx (1)

229-246: Minor inefficiency: duplicate registry lookup.

When hasPersistedSession returns true (line 231), getSessionByCwd is called (line 233). When it returns false, getSessionByCwd is called again (line 238). This results in two registry loads for the case where the session file is missing but registry entry exists.

This is functionally correct but could be optimised by moving the registry lookup earlier and reusing the result.

- Add restrictive file permissions (0o700 for dir, 0o600 for file)
- Implement file-level locking using O_EXCL for cross-process safety
- Add atomic write pattern (temp file + fsync + rename) for durability
- Ensure permissions are enforced on existing files via chmod
- Add comprehensive tests for concurrent operations and data integrity

The registry now properly handles:
- Multiple processes accessing the registry simultaneously
- Crash recovery with stale lock timeout detection
- Atomic file updates to prevent corruption
- Secure file permissions to protect session data
Copy link
Contributor

@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: 3

🤖 Fix all issues with AI agents
In `@src/session/registry.ts`:
- Around line 175-192: The validation in loadRegistryInternal accepts null or
arrays because it only checks typeof parsed.sessions === 'object'; update the
condition to ensure parsed.sessions is a plain, non-null object (e.g.,
parsed.version === 1 && parsed.sessions && !Array.isArray(parsed.sessions)
and/or check Object.prototype.toString.call(parsed.sessions) === '[object
Object]') so that loadRegistryInternal returns a clean { version: 1, sessions:
{} } when sessions is null/array/other non-plain types; adjust the parsed
validation and keep the same fallback behavior.
- Around line 122-154: The acquireLock loop currently swallows all errors from
unlink(lockPath) when attempting to remove a stale lock, which allows an
infinite hang if unlink fails (e.g., read‑only FS); modify the stale‑lock branch
inside acquireLock to capture the unlink error and if unlink fails with anything
other than ENOENT (or if unlink fails after LOCK_TIMEOUT_MS) rethrow or return a
clear error so the caller won't loop forever; reference acquireLock, lockPath,
unlink, LOCK_TIMEOUT_MS and LOCK_RETRY_DELAY_MS when making this change.
- Around line 365-381: The current cleanupStaleRegistryEntries holds
withRegistryLock while awaiting checkSessionExists for each registry.sessions
entry; instead, snapshot registry.sessions (map sessionId -> cwd), release the
lock, run checkSessionExists for each cwd and collect stale sessionIds, then
reacquire withRegistryLock and in a short critical section delete only those
sessionIds whose registry.sessions[sessionId] still exists and still has the
same cwd (to avoid race) and increment cleaned; reference functions/values:
cleanupStaleRegistryEntries, withRegistryLock, registry.sessions,
checkSessionExists, and ensure LOCK_TIMEOUT_MS is not hit by long checks being
done outside the lock.

…erage

Export formatSessionEntry, listSessions, cleanupRegistry, and resolveSession
functions from resume.tsx to enable direct unit testing. Add tests covering
all argument parsing, session entry formatting, session listing with mocked
registry, cleanup behavior, and session resolution across all code paths
(exact match, prefix match, multiple matches, missing sessions, etc.).
Copy link
Contributor

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands/resume.tsx (1)

566-635: Update registry status to “running” on resume start.
Without this, listings can show “paused/interrupted” even while a resumed session is active.

🔧 Suggested patch
   const resumedState = resumePersistedSession(persistedState);
   await savePersistedSession(resumedState);
+  if (registryEntry) {
+    await updateRegistryStatus(registryEntry.sessionId, 'running');
+  }
 
   const summary = getSessionSummary(resumedState);
🤖 Fix all issues with AI agents
In `@src/commands/resume.tsx`:
- Around line 193-227: When resolving a session by registry ID in
resolveSession, validate that the session's backing file actually exists before
returning the registry entry: after you obtain the SessionRegistryEntry (from
getSessionById or findSessionsByPrefix), check the session file path referenced
by entry (e.g. entry.sessionFile or entry.cwd-derived path) and if the file is
missing log a clear, specific message (same cleanup guidance used in the generic
load failure path) prompting the user to remove the stale registry entry or
recreate the session, then return null instead of returning the stale
registryEntry; update logging to include args.sessionId and reuse the existing
user-facing cleanup guidance text.

claude and others added 4 commits January 22, 2026 21:15
Registry improvements (PR feedback):
- loadRegistryInternal: validate sessions is non-null, non-array plain object
- acquireLock: throw clear error if stale lock unlink fails (prevent hang)
- cleanupStaleRegistryEntries: two-phase approach releases lock during I/O
  checks to avoid LOCK_TIMEOUT_MS being hit by slow existence checks

CI fix (root cause of 10% patch coverage):
- Add src/session/ test batch to ci.yml workflow so registry.test.ts runs
- Add session.lcov to Codecov upload file list
- The registry tests were never executed in CI because src/session/ was
  missing from the test batch list
When resolveSession finds a registry entry by session ID (exact or prefix
match), verify the backing session file still exists at entry.cwd before
returning. If the file is missing, log a clear error including the session
ID and cleanup guidance, then return null instead of a stale entry.
@subsy subsy merged commit 6b0b291 into main Jan 22, 2026
9 checks passed
@subsy subsy deleted the claude/investigate-issue-175-QkNwJ branch January 22, 2026 21:29
sakaman pushed a commit to sakaman/ralph-tui that referenced this pull request Feb 15, 2026
…QkNwJ

Add cross-directory session resume via global registry
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.

2 participants

Comments