Skip to content

feat: auto-restore sessions opened in provider GUI (Fixes #32) - #33

Merged
YosefHayim merged 1 commit into
mainfrom
feat/32-gui-auto-restore
Aug 8, 2026
Merged

YosefHayim merged 1 commit into
mainfrom
feat/32-gui-auto-restore

Conversation

@YosefHayim

@YosefHayim YosefHayim commented Aug 8, 2026 •

Copy link
Copy Markdown
Owner

User description

Fixes #32

Summary

  • Pack leaves stubs at the original path (tiny marker) so sessions stay listable after compression.
  • watch materializes a stub when the provider opens/touches it (GUI session pick).
  • Wrappers now run watch --provider X -- <real-cli> … so Grok/Codex/Claude launches get this without a second command.
  • Restore still never overwrites a real live session that differs from the archive.

Confidence

8/10 — Unit tests for stubs/watch/pack+restore; GUI behavior depends on the provider writing/touching the stub path (Grok multi-file dirs work well; pure read-only opens may need a touch/event).

Test plan

  • pnpm test 128 pass
  • pnpm typecheck && pnpm check:ci && pnpm build
  • Hosted CI Gate

Main safety

  • Branch: feat/32-gui-auto-restore

Summary by cubic

Auto-restore archived sessions when opened from a provider GUI by leaving tiny stubs on pack and running a new watch process to materialize them on open. This keeps sessions listable and removes the need for a second command.

  • New Features
    • Pack leaves lightweight stub markers at original paths; opening a stub in the GUI triggers restore.
    • Added watch CLI to monitor stubs and restore on access; supports provider filter, JSON events, and polling fallback.
    • Lifecycle wrappers now exec watch --provider <X> -- <real-bin> … after preflight so Grok/Codex/Claude launches auto-restore.
    • Restore detects and removes stubs before writing and still never overwrites changed live sessions.

Written for commit ec6e528. Summary will update on new commits.

Review in cubic


CodeAnt-AI Description

Restore packed sessions automatically when selected in provider apps

What Changed

  • Packed sessions now leave small listable stubs at their original locations instead of disappearing from provider session lists.
  • Provider wrappers monitor these stubs and restore the full session when a GUI opens or activates one.
  • Restoring a stub removes only the marker; live sessions that differ from the archive still produce a conflict and are not overwritten.
  • Added a standalone watch command with provider filtering, polling, JSON events, and support for running a provider process.

Impact

✅ Packed sessions remain visible in provider GUIs
✅ GUI-selected sessions restore without a separate command
✅ Fewer accidental overwrites of active sessions

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

When packing, leave tiny listable stubs so providers can still show
sessions. Wrappers now run `watch` with the real binary so opening a
stub in the GUI materializes the full archive without an extra command.

Fixes #32
@changeset-bot

changeset-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: ec6e528

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@codeant-ai

codeant-ai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR ec6e528 Aug 08, 2026 · 17:33 17:36

@codeant-ai

codeant-ai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@YosefHayim, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d04e4cc-54fb-4bef-925c-f1554ad8c293

📥 Commits

Reviewing files that changed from the base of the PR and between 9b43d0f and ec6e528.

📒 Files selected for processing (12)
  • README.md
  • src/cli/commands/packAllProvidersCommand.test.ts
  • src/cli/commands/watchCommand.ts
  • src/cli/main.ts
  • src/cli/mainArgs.ts
  • src/core/lifecycleWrappers.ts
  • src/core/sessionArchive.test.ts
  • src/core/sessionArchive.ts
  • src/core/sessionStub.test.ts
  • src/core/sessionStub.ts
  • src/core/sessionWatch.test.ts
  • src/core/sessionWatch.ts

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.

@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Aug 8, 2026
@YosefHayim
YosefHayim merged commit 6117510 into main Aug 8, 2026
9 checks passed
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Auto-restore packed sessions opened via provider GUI using stubs + watch

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Leave archived-session stubs on pack so provider GUIs still list sessions.
• Add watch to auto-restore when a provider opens/touches a stub path.
• Update wrappers/restore logic and add unit tests for stubs + watch flows.
Diagram

graph TD
PA["Provider app"] --> WR["Lifecycle wrapper"] --> WC["watch cmd"] --> SW["core sessionWatch"] --> SA["core sessionArchive"] --> V[("Vault")]
SA --> SS["core sessionStub"] --> SP[("Session path")]
SP --> SW
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Symlink-based placeholders
  • ➕ No custom stub format; GUIs may follow links naturally
  • ➕ Can point directly at a restore command or metadata location
  • ➖ Symlink semantics differ across OS/filesystems (Windows/admin policies)
  • ➖ Providers may not follow or may write through symlinks unpredictably
  • ➖ Harder to represent directory sessions safely without confusion
2. Use a higher-level file watcher (e.g., chokidar) with platform backends
  • ➕ More reliable change detection across platforms than bare fs.watch
  • ➕ Better handling of missing paths, renames, and directory trees
  • ➖ Adds a dependency and potentially more runtime overhead
  • ➖ Still cannot guarantee detection for pure read-only opens

Recommendation: Keep the PR’s stub + watch approach: it preserves GUI listability, stays largely cross-platform, and keeps restore safety via conflict checks. Consider upgrading the watcher implementation (e.g., chokidar) only if fs.watch reliability becomes a real-world issue; the stub format and restore semantics are a solid foundation either way.

Files changed (12) +825 / -14

Enhancement (6) +635 / -5
watchCommand.tsAdd 'watch' CLI command to follow provider and restore stubs +221/-0

Add 'watch' CLI command to follow provider and restore stubs

• Introduces a new citty subcommand that watches session stub paths and triggers restore events when opened. Supports provider filtering, JSON event output, polling interval control, and optionally spawning/following a child provider process after '--'.

src/cli/commands/watchCommand.ts

main.tsRegister 'watch' subcommand in CLI entrypoint +2/-0

Register 'watch' subcommand in CLI entrypoint

• Adds the new watch command to the root command’s subcommand map so it’s available in the public CLI.

src/cli/main.ts

lifecycleWrappers.tsWrappers now exec 'watch' while running provider binary +5/-0

Wrappers now exec 'watch' while running provider binary

• Updates generated lifecycle wrapper scripts to run 'watch --provider <id> -- <real-bin> ...' after best-effort preflight. This enables auto-restore when sessions are opened from provider GUIs without needing session IDs in argv.

src/core/lifecycleWrappers.ts

sessionArchive.tsWrite stubs on pack and treat stubs specially on restore +38/-5

Write stubs on pack and treat stubs specially on restore

• After packing and removing originals, writes an archived-session stub so sessions remain discoverable by GUIs. During restore, detects if an existing original is a stub and removes it to allow restore to proceed, while still returning 'conflict' for real mismatched live sessions.

src/core/sessionArchive.ts

sessionStub.tsIntroduce archived session stub format and helpers +192/-0

Introduce archived session stub format and helpers

• Adds a stub marker schema and helpers to write stubs for file and directory sessions, detect whether a path is a stub, and determine whether a directory stub was opened based on additional files. Provides a consistent magic marker ('agent-session-pack-archived-stub') for GUI-triggered materialization.

src/core/sessionStub.ts

sessionWatch.tsAdd stub watcher that restores on provider open signals +177/-0

Add stub watcher that restores on provider open signals

• Implements a long-running watcher that lists vault manifests, registers fs.watch handlers on original paths, and polls as a fallback. When a stub is detected and an open signal occurs (fs event for files; directory touch detection for directories), it invokes ensureSessionRestored and emits an event callback.

src/core/sessionWatch.ts

Tests (4) +182 / -5
packAllProvidersCommand.test.tsAdjust pack tests to expect stubs instead of deletion +4/-2

Adjust pack tests to expect stubs instead of deletion

• Replaces the expectation that packed session paths no longer exist with assertions that a stub marker remains at the original path. This aligns the tests with the new GUI-listable stub behavior.

src/cli/commands/packAllProvidersCommand.test.ts

sessionArchive.test.tsUpdate archive tests to validate stub presence after pack +12/-3

Update archive tests to validate stub presence after pack

• Changes assertions to confirm that packing leaves behind a stub JSON marker at the original session path. Also ensures lifecycle-disabled scenarios still preserve the stub rather than removing the path.

src/core/sessionArchive.test.ts

sessionStub.test.tsAdd unit tests for stub write/detect semantics +53/-0

Add unit tests for stub write/detect semantics

• Adds tests for file stubs (write + detect) and directory stubs (marker file + open/touch detection via non-marker files). These validate the core stub contract used by pack/watch.

src/core/sessionStub.test.ts

sessionWatch.test.tsAdd unit test proving watch materializes stubs on activity +113/-0

Add unit test proving watch materializes stubs on activity

• Introduces a watch test using a deterministic copy-based compression adapter and a short polling interval. Simulates a provider touching a file stub and asserts the watcher restores the original content and emits a restore event.

src/core/sessionWatch.test.ts

Documentation (1) +7 / -4
README.mdDocument stub + watch GUI auto-restore behavior +7/-4

Document stub + watch GUI auto-restore behavior

• Updates wrapper documentation to explain that wrappers run both preflight and watch. Clarifies that pack leaves listable stubs and watch materializes sessions when a GUI opens them, while still never overwriting conflicting live sessions.

README.md

Other (1) +1 / -0
mainArgs.tsAdd '--watch' command-flag alias +1/-0

Add '--watch' command-flag alias

• Extends the command flag alias table to map '--watch' to the 'watch' subcommand, consistent with other command aliases.

src/cli/mainArgs.ts

Comment on lines +142 to +149
const child = spawn(command, commandArgs, {
stdio: 'inherit',
env: process.env,
});

child.on('exit', (code) => {
childExitCode = code ?? 0;
stop = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The spawned provider is not cleaned up when watchSessionStubs fails. The provider child can remain running with inherited stdio while the watch command has already failed, leaving an orphaned provider process. Keep the child handle and terminate or await it in a failure cleanup path; also handle the child error event explicitly. [missing cleanup]

Severity Level: Major ⚠️
- ⚠️ Wrapped provider can outlive failed watcher command.
- ⚠️ Orphaned processes retain provider resources and stdio.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/cli/commands/watchCommand.ts
**Line:** 142:149
**Comment:**
	*Missing Cleanup: The spawned provider is not cleaned up when `watchSessionStubs` fails. The provider child can remain running with inherited stdio while the watch command has already failed, leaving an orphaned provider process. Keep the child handle and terminate or await it in a failure cleanup path; also handle the child `error` event explicitly.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment thread src/core/sessionWatch.ts
try {
const watcher = watch(manifest.originalPath, { persistent: true }, () => {
// Any FS event on a stub is treated as provider open intent.
void materializeIfNeeded(manifest, true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The asynchronous restore launched from the filesystem callback is detached with void, so failures from ensureSessionRestored become unhandled promise rejections instead of being returned through the declared Effect error channel. A corrupt archive or filesystem failure can therefore leave the watcher running while the caller receives no failure status. Catch the callback promise and route the failure into the watch lifecycle or a reported error state. [possible bug]

Severity Level: Major ⚠️
- ⚠️ Corrupt archives fail without watcher status reporting.
- ⚠️ GUI opens can leave the stub unmaterialized.
- ⚠️ Detached failures bypass `SessionWatchError` handling.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/core/sessionWatch.ts
**Line:** 95:95
**Comment:**
	*Possible Bug: The asynchronous restore launched from the filesystem callback is detached with `void`, so failures from `ensureSessionRestored` become unhandled promise rejections instead of being returned through the declared Effect error channel. A corrupt archive or filesystem failure can therefore leave the watcher running while the caller receives no failure status. Catch the callback promise and route the failure into the watch lifecycle or a reported error state.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment thread src/core/sessionWatch.ts
Comment on lines +154 to +155
while (!request.shouldStop()) {
const manifests = yield* listVaultSessionManifests(request.vaultPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The watcher handles are closed only after the polling loop exits normally. If listVaultSessionManifests or another polling operation fails, the Effect exits before this cleanup runs, while the persistent: true filesystem watchers remain active and can keep the process alive. Put watcher cleanup in a guaranteed finalizer or try/finally path. [resource leak]

Severity Level: Major ⚠️
- ⚠️ Vault read failures bypass filesystem watcher cleanup.
- ⚠️ Persistent handles can keep failed watch processes alive.
- ⚠️ Later stub events may invoke stale callbacks.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/core/sessionWatch.ts
**Line:** 154:155
**Comment:**
	*Resource Leak: The watcher handles are closed only after the polling loop exits normally. If `listVaultSessionManifests` or another polling operation fails, the Effect exits before this cleanup runs, while the `persistent: true` filesystem watchers remain active and can keep the process alive. Put watcher cleanup in a guaranteed finalizer or `try/finally` path.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎


# Follow the provider process and materialize GUI-opened stubs (no session id in argv).
if command -v node >/dev/null 2>&1; then
exec node "$CLI_ENTRY" watch --provider "$PROVIDER" -- "$REAL_BIN" "$@"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The wrapper now starts the real provider only through watch, but unlike the preflight call there is no best-effort fallback. Any watch initialization error, such as an unreadable or malformed setup configuration or vault, causes the wrapper to exit before launching the provider. Preserve the direct provider fallback when watcher startup fails. [error handling]

Severity Level: Major ⚠️
- ❌ Wrapped provider launch fails with invalid setup configuration.
- ⚠️ Codex, Claude, Grok, and other wrappers become unavailable.
- ⚠️ Provider launch depends on watcher configuration availability.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/core/lifecycleWrappers.ts
**Line:** 165:165
**Comment:**
	*Error Handling: The wrapper now starts the real provider only through `watch`, but unlike the preflight call there is no best-effort fallback. Any watch initialization error, such as an unreadable or malformed setup configuration or vault, causes the wrapper to exit before launching the provider. Preserve the direct provider fallback when watcher startup fails.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +641 to +644
if (isStub) {
yield* removePath(request.originalPath);
return 'continue-restore' as const;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The stub is deleted before archive extraction and verification complete. A corrupt, unavailable, or incompatible archive therefore leaves the provider path missing instead of retaining the listable stub for a later retry. Defer deletion until restoration succeeds, or recreate the stub on every restore failure. [state/lifecycle]

Severity Level: Major ⚠️
- ⚠️ Corrupt archives remove GUI-visible session stubs.
- ⚠️ Provider session lists hide sessions during failed restore.
- ⚠️ Users must retry restoration manually.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/core/sessionArchive.ts
**Line:** 641:644
**Comment:**
	*State Lifecycle: The stub is deleted before archive extraction and verification complete. A corrupt, unavailable, or incompatible archive therefore leaves the provider path missing instead of retaining the listable stub for a later retry. Defer deletion until restoration succeeds, or recreate the stub on every restore failure.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (3) 📜 Skill insights (0)

Grey Divider


Action required

1. Unhandled watch rejections 🐞 Bug ☼ Reliability
Description
The fs.watch callback fires async materializeIfNeeded() and discards its Promise; if
ensureSessionRestored() fails, the rejection is unhandled and can terminate or destabilize the watch
process (and wrappers). This can cause missed restores and/or unexpected wrapper failures during
normal provider usage.
Code

src/core/sessionWatch.ts[R93-96]

+          const watcher = watch(manifest.originalPath, { persistent: true }, () => {
+            // Any FS event on a stub is treated as provider open intent.
+            void materializeIfNeeded(manifest, true);
+          });
Evidence
The new watcher callback drops the Promise from async restore work; that restore work can reject,
which will escape the callback without any handler.

src/core/sessionWatch.ts[92-97]
src/core/sessionWatch.ts[111-145]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`fs.watch` invokes an async function (`materializeIfNeeded`) but its Promise is not handled. Any rejection (e.g., from `ensureSessionRestored`) becomes an unhandled rejection, which may terminate the process depending on runtime settings.

### Issue Context
This happens in the new `watchSessionStubs` implementation and impacts lifecycle wrappers that now exec `watch`.

### Fix Focus Areas
- src/core/sessionWatch.ts[92-96]
- src/core/sessionWatch.ts[111-152]

### What to change
- In the `fs.watch` callback, explicitly handle the returned Promise:
 - `materializeIfNeeded(...).catch(err => onEvent({status:'error', ...}) / log to stderr)`
 - or refactor `materializeIfNeeded` into an `Effect` and run it via an Effect runtime/fiber with structured error handling.
- Add basic per-session/path deduping (optional but recommended): avoid concurrent restores for the same `originalPath` when multiple events fire in quick succession.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Child exit misreported 🐞 Bug ≡ Correctness
Description
watchCommand doesn’t handle child process spawn errors and treats signal-based termination as exit
code 0 (code ?? 0), so wrappers can incorrectly report success or crash on failed spawns. This
breaks the wrapper contract of returning the provider’s real exit status.
Code

src/cli/commands/watchCommand.ts[R147-150]

+      child.on('exit', (code) => {
+        childExitCode = code ?? 0;
+        stop = true;
+      });
Evidence
The child process is spawned without an error handler, and the exit handler explicitly converts null
exit codes (signals) into 0; then the command sets process.exitCode from that value.

src/cli/commands/watchCommand.ts[134-150]
src/cli/commands/watchCommand.ts[183-185]
src/core/lifecycleWrappers.ts[158-168]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
When `watch` follows a child process:
- a failed spawn emits an `error` event which is currently unhandled,
- the `exit` handler maps `code === null` (signal termination) to `0`, reporting success.

### Issue Context
Lifecycle wrappers now `exec ... watch -- <real-bin> ...`, so `watch` must accurately propagate provider termination.

### Fix Focus Areas
- src/cli/commands/watchCommand.ts[142-150]
- src/cli/commands/watchCommand.ts[183-185]

### What to change
- Add `child.on('error', err => { childExitCode = 127 (or 1); stop = true; /* log */ })`.
- Update `child.on('exit', (code, signal) => { ... })` to handle `signal`:
 - map to a non-zero exit code (commonly `128 + signalNumber` if available), or
 - at minimum treat `code === null` as non-zero and include signal name in stderr output.
- Ensure `process.exitCode` reflects the derived exit status.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Wrapper invokes watch command 📘 Rule violation ⛨ Security
Description
The provider wrapper now executes agent-session-pack watch, which performs filesystem operations
on real local session paths outside the approved CLI entry points. This violates the restriction
that only pnpm savings, pnpm evidence:local, or agent-session-pack check may reach real
session-directory access code.
Code

src/core/lifecycleWrappers.ts[R163-166]

+# Follow the provider process and materialize GUI-opened stubs (no session id in argv).
+if command -v node >/dev/null 2>&1; then
+  exec node "$CLI_ENTRY" watch --provider "$PROVIDER" -- "$REAL_BIN" "$@"
+fi
Evidence
The compliance rule restricts real session-directory access to three approved entry points, but the
wrapper now runs watch, and watch resolves home/vault paths and starts watching/restoring
sessions (i.e., real filesystem access) during normal provider execution.

Rule 2297829: Restrict local session directory access to approved CLIs only
src/core/lifecycleWrappers.ts[163-166]
src/cli/commands/watchCommand.ts[121-129]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`watch` is a new CLI entry point that watches/restores sessions by accessing real local session directories, but the compliance policy restricts such access to specific approved entry points only.

## Issue Context
`src/core/lifecycleWrappers.ts` now automatically runs `watch` for provider wrappers, making real session-directory access reachable from many non-approved entrypoints.

## Fix Focus Areas
- src/core/lifecycleWrappers.ts[163-166]
- src/cli/commands/watchCommand.ts[121-129]

Potential approaches:
- Add an explicit runtime guard (similar to an `assertApprovedEntryPoint`) that only permits real session-dir access when invoked via the approved entry points (or a dedicated, explicitly-approved env var).
- If `watch` is intended to be an approved entry point, update the project’s entrypoint/guard conventions so `watch` is treated equivalently (and ensure wrappers invoke only the guarded path).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Stub delete lacks byte backup 📘 Rule violation ☼ Reliability
Description
The restore path deletes an archived stub at originalPath via removePath() without first
preserving the original bytes to a backup/archive location. This risks data loss on destructive
operations and violates the required pre-delete byte preservation step.
Code

src/core/sessionArchive.ts[R640-643]

+    const isStub = yield* isArchivedStubPath(request.originalPath, request.sourceKind);
+    if (isStub) {
+      yield* removePath(request.originalPath);
+      return 'continue-restore' as const;
Evidence
The rule requires reading and writing the exact bytes to a backup location immediately before any
delete/overwrite. The code deletes request.originalPath when it is a stub without any preceding
backup operation in that code path.

Rule 2297838: Preserve original file bytes before deletion operations
src/core/sessionArchive.ts[640-643]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A destructive deletion (`removePath(request.originalPath)`) happens when the existing path is detected as an archived stub, but no byte-preserving backup is performed immediately before deletion.

## Issue Context
Even if the path is “only a stub”, the compliance rule requires preserving exact bytes before deletion operations.

## Fix Focus Areas
- src/core/sessionArchive.ts[640-643]

Potential approaches:
- Introduce and use a helper like `removePathWithBackup({ path, vaultPath })` that reads raw bytes and writes them to a deterministic backup location before deletion.
- Alternatively, replace deletion with a safe rename/move into the vault (or another configured backup area) before continuing restore.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. poll-ms parsed without Schema 📘 Rule violation ⚙ Maintainability
Description
The watch CLI boundary uses ad-hoc parsing for poll-ms via Number(...) checks rather than
Effect Schema validation. This reduces consistency and makes it easier for invalid inputs to bypass
structured boundary validation.
Code

src/cli/commands/watchCommand.ts[R210-218]

+const parsePollMs = (value: string | undefined): number => {
+  if (value === undefined) {
+    return 750;
+  }
+
+  const parsed = Number(value);
+  if (!Number.isFinite(parsed) || parsed < 100) {
+    return 750;
+  }
Evidence
watchCommand is a CLI boundary; the rule requires Effect Schema validation at boundaries where
feasible. The added parsePollMs performs manual parsing/validation instead of using Schema, even
though Schema is already imported in the module.

Rule 2619041: Prefer Effect Schema validation at module boundaries over ad-hoc checks
src/cli/commands/watchCommand.ts[210-218]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`poll-ms` is validated with manual numeric parsing instead of Effect Schema at the CLI boundary.

## Issue Context
The compliance rule prefers Effect Schema validation at module boundaries (like CLI parsing) over ad-hoc checks when schemas can express the constraints.

## Fix Focus Areas
- src/cli/commands/watchCommand.ts[210-218]

Potential approaches:
- Define an Effect Schema for `pollMs` (e.g., `Schema.NumberFromString` + bounds/refinement) and decode it, returning a typed error/effect on invalid input.
- Optionally define a full args schema for `WatchArgs` and parse/decode once at the boundary, rather than per-field parsing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
6. Watch loop is I/O heavy 🐞 Bug ➹ Performance
Description
watchSessionStubs re-reads all vault manifests every poll interval and reads/parses each original
session file to check for stub markers, even after sessions have been restored. This can create
sustained disk I/O and CPU overhead while a provider runs (especially with many manifests or large
session files).
Code

src/core/sessionWatch.ts[R154-157]

+    while (!request.shouldStop()) {
+      const manifests = yield* listVaultSessionManifests(request.vaultPath);
+      const scoped = manifests.filter(
+        (manifest) => request.provider === undefined || manifest.provider === request.provider,
Evidence
The main loop re-lists and re-reads manifests repeatedly, then calls stub detection that reads
entire files from disk to parse JSON markers.

src/core/sessionWatch.ts[154-171]
src/core/sessionArchive.ts[293-296]
src/core/sessionArchive.ts[727-739]
src/core/manifestStore.ts[79-95]
src/core/manifestStore.ts[108-118]
src/core/sessionStub.ts[102-118]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The watch loop polls by repeatedly:
1) listing+reading all manifests from disk, and
2) reading full contents of `manifest.originalPath` to detect file stubs.
This can be expensive at runtime.

### Issue Context
This affects the new `watch` feature that wrappers run continuously while the provider runs.

### Fix Focus Areas
- src/core/sessionWatch.ts[154-171]
- src/core/sessionWatch.ts[115-132]
- src/core/sessionStub.ts[102-118]
- src/core/manifestStore.ts[79-95]
- src/core/manifestStore.ts[108-118]

### What to change
- Avoid re-reading all manifests every poll:
 - cache manifests in memory and refresh less frequently, or
 - add a watcher on the vault manifest directory to update the set when manifests change.
- Avoid reading full session files to detect stubs:
 - for file stubs, read only a small prefix (or use a lightweight marker strategy),
 - short-circuit checks once a path is known to be fully restored (stop checking/stop watching if appropriate).
- Consider making polling only a fallback for watcher registration/missed events, rather than scanning all manifests and paths continuously.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 21 rules

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +163 to +166
# Follow the provider process and materialize GUI-opened stubs (no session id in argv).
if command -v node >/dev/null 2>&1; then
exec node "$CLI_ENTRY" watch --provider "$PROVIDER" -- "$REAL_BIN" "$@"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Wrapper invokes watch command 📘 Rule violation ⛨ Security

The provider wrapper now executes agent-session-pack watch, which performs filesystem operations
on real local session paths outside the approved CLI entry points. This violates the restriction
that only pnpm savings, pnpm evidence:local, or agent-session-pack check may reach real
session-directory access code.
Agent Prompt
## Issue description
`watch` is a new CLI entry point that watches/restores sessions by accessing real local session directories, but the compliance policy restricts such access to specific approved entry points only.

## Issue Context
`src/core/lifecycleWrappers.ts` now automatically runs `watch` for provider wrappers, making real session-directory access reachable from many non-approved entrypoints.

## Fix Focus Areas
- src/core/lifecycleWrappers.ts[163-166]
- src/cli/commands/watchCommand.ts[121-129]

Potential approaches:
- Add an explicit runtime guard (similar to an `assertApprovedEntryPoint`) that only permits real session-dir access when invoked via the approved entry points (or a dedicated, explicitly-approved env var).
- If `watch` is intended to be an approved entry point, update the project’s entrypoint/guard conventions so `watch` is treated equivalently (and ensure wrappers invoke only the guarded path).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +640 to +643
const isStub = yield* isArchivedStubPath(request.originalPath, request.sourceKind);
if (isStub) {
yield* removePath(request.originalPath);
return 'continue-restore' as const;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Stub delete lacks byte backup 📘 Rule violation ☼ Reliability

The restore path deletes an archived stub at originalPath via removePath() without first
preserving the original bytes to a backup/archive location. This risks data loss on destructive
operations and violates the required pre-delete byte preservation step.
Agent Prompt
## Issue description
A destructive deletion (`removePath(request.originalPath)`) happens when the existing path is detected as an archived stub, but no byte-preserving backup is performed immediately before deletion.

## Issue Context
Even if the path is “only a stub”, the compliance rule requires preserving exact bytes before deletion operations.

## Fix Focus Areas
- src/core/sessionArchive.ts[640-643]

Potential approaches:
- Introduce and use a helper like `removePathWithBackup({ path, vaultPath })` that reads raw bytes and writes them to a deterministic backup location before deletion.
- Alternatively, replace deletion with a safe rename/move into the vault (or another configured backup area) before continuing restore.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +210 to +218
const parsePollMs = (value: string | undefined): number => {
if (value === undefined) {
return 750;
}

const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed < 100) {
return 750;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. poll-ms parsed without schema 📘 Rule violation ⚙ Maintainability

The watch CLI boundary uses ad-hoc parsing for poll-ms via Number(...) checks rather than
Effect Schema validation. This reduces consistency and makes it easier for invalid inputs to bypass
structured boundary validation.
Agent Prompt
## Issue description
`poll-ms` is validated with manual numeric parsing instead of Effect Schema at the CLI boundary.

## Issue Context
The compliance rule prefers Effect Schema validation at module boundaries (like CLI parsing) over ad-hoc checks when schemas can express the constraints.

## Fix Focus Areas
- src/cli/commands/watchCommand.ts[210-218]

Potential approaches:
- Define an Effect Schema for `pollMs` (e.g., `Schema.NumberFromString` + bounds/refinement) and decode it, returning a typed error/effect on invalid input.
- Optionally define a full args schema for `WatchArgs` and parse/decode once at the boundary, rather than per-field parsing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/core/sessionWatch.ts
Comment on lines +93 to +96
const watcher = watch(manifest.originalPath, { persistent: true }, () => {
// Any FS event on a stub is treated as provider open intent.
void materializeIfNeeded(manifest, true);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Unhandled watch rejections 🐞 Bug ☼ Reliability

The fs.watch callback fires async materializeIfNeeded() and discards its Promise; if
ensureSessionRestored() fails, the rejection is unhandled and can terminate or destabilize the watch
process (and wrappers). This can cause missed restores and/or unexpected wrapper failures during
normal provider usage.
Agent Prompt
### Issue description
`fs.watch` invokes an async function (`materializeIfNeeded`) but its Promise is not handled. Any rejection (e.g., from `ensureSessionRestored`) becomes an unhandled rejection, which may terminate the process depending on runtime settings.

### Issue Context
This happens in the new `watchSessionStubs` implementation and impacts lifecycle wrappers that now exec `watch`.

### Fix Focus Areas
- src/core/sessionWatch.ts[92-96]
- src/core/sessionWatch.ts[111-152]

### What to change
- In the `fs.watch` callback, explicitly handle the returned Promise:
  - `materializeIfNeeded(...).catch(err => onEvent({status:'error', ...}) / log to stderr)`
  - or refactor `materializeIfNeeded` into an `Effect` and run it via an Effect runtime/fiber with structured error handling.
- Add basic per-session/path deduping (optional but recommended): avoid concurrent restores for the same `originalPath` when multiple events fire in quick succession.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/core/sessionWatch.ts
Comment on lines +154 to +157
while (!request.shouldStop()) {
const manifests = yield* listVaultSessionManifests(request.vaultPath);
const scoped = manifests.filter(
(manifest) => request.provider === undefined || manifest.provider === request.provider,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

5. Watch loop is i/o heavy 🐞 Bug ➹ Performance

watchSessionStubs re-reads all vault manifests every poll interval and reads/parses each original
session file to check for stub markers, even after sessions have been restored. This can create
sustained disk I/O and CPU overhead while a provider runs (especially with many manifests or large
session files).
Agent Prompt
### Issue description
The watch loop polls by repeatedly:
1) listing+reading all manifests from disk, and
2) reading full contents of `manifest.originalPath` to detect file stubs.
This can be expensive at runtime.

### Issue Context
This affects the new `watch` feature that wrappers run continuously while the provider runs.

### Fix Focus Areas
- src/core/sessionWatch.ts[154-171]
- src/core/sessionWatch.ts[115-132]
- src/core/sessionStub.ts[102-118]
- src/core/manifestStore.ts[79-95]
- src/core/manifestStore.ts[108-118]

### What to change
- Avoid re-reading all manifests every poll:
  - cache manifests in memory and refresh less frequently, or
  - add a watcher on the vault manifest directory to update the set when manifests change.
- Avoid reading full session files to detect stubs:
  - for file stubs, read only a small prefix (or use a lightweight marker strategy),
  - short-circuit checks once a path is known to be fully restored (stop checking/stop watching if appropriate).
- Consider making polling only a fallback for watcher registration/missed events, rather than scanning all manifests and paths continuously.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +147 to +150
child.on('exit', (code) => {
childExitCode = code ?? 0;
stop = true;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

6. Child exit misreported 🐞 Bug ≡ Correctness

watchCommand doesn’t handle child process spawn errors and treats signal-based termination as exit
code 0 (code ?? 0), so wrappers can incorrectly report success or crash on failed spawns. This
breaks the wrapper contract of returning the provider’s real exit status.
Agent Prompt
### Issue description
When `watch` follows a child process:
- a failed spawn emits an `error` event which is currently unhandled,
- the `exit` handler maps `code === null` (signal termination) to `0`, reporting success.

### Issue Context
Lifecycle wrappers now `exec ... watch -- <real-bin> ...`, so `watch` must accurately propagate provider termination.

### Fix Focus Areas
- src/cli/commands/watchCommand.ts[142-150]
- src/cli/commands/watchCommand.ts[183-185]

### What to change
- Add `child.on('error', err => { childExitCode = 127 (or 1); stop = true; /* log */ })`.
- Update `child.on('exit', (code, signal) => { ... })` to handle `signal`:
  - map to a non-zero exit code (commonly `128 + signalNumber` if available), or
  - at minimum treat `code === null` as non-zero and include signal name in stderr output.
- Ensure `process.exitCode` reflects the derived exit status.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@YosefHayim
YosefHayim deleted the feat/32-gui-auto-restore branch September 27, 2026 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: auto-restore when opening sessions in provider GUI (stubs + watch)

1 participant