Skip to content

feat: Persist and restore active session id (resume in last open sess…#714

Open
scriptease wants to merge 1 commit intoRunMaestro:rcfrom
scriptease:persist-activesessionid
Open

feat: Persist and restore active session id (resume in last open sess…#714
scriptease wants to merge 1 commit intoRunMaestro:rcfrom
scriptease:persist-activesessionid

Conversation

@scriptease
Copy link
Copy Markdown

@scriptease scriptease commented Apr 3, 2026

Before:

Maestro would always start with session [0] from maestro-sessions.json

After:

Persist activeSessionId in the maestro-sessions.json and resume the last open session when Maestro launches, ignore when not found.

{ "sessions": [...], "activeSessionId": "c97dca4d-172f-471e-880c-7e694c59f9f4" }

Summary by CodeRabbit

  • New Features

    • The app now persists and restores the active session across launches, reactivating your last valid session and falling back to a sensible default when needed.
  • Refactor

    • Active-session selection now prefers the persisted value when valid and synchronizes state with the host when it changes.
  • Tests

    • Updated tests to cover active-session persistence, restoration, and IPC handler behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 15574df2-5ceb-45dc-a309-de68fa7d8c3b

📥 Commits

Reviewing files that changed from the base of the PR and between e5d3580 and 5249abb.

📒 Files selected for processing (9)
  • src/__tests__/main/ipc/handlers/persistence.test.ts
  • src/__tests__/renderer/hooks/useSessionRestoration.test.ts
  • src/__tests__/setup.ts
  • src/main/ipc/handlers/persistence.ts
  • src/main/preload/settings.ts
  • src/main/stores/types.ts
  • src/renderer/global.d.ts
  • src/renderer/hooks/session/useSessionRestoration.ts
  • src/renderer/stores/sessionStore.ts
✅ Files skipped from review due to trivial changes (1)
  • src/main/stores/types.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/main/preload/settings.ts
  • src/tests/main/ipc/handlers/persistence.test.ts
  • src/renderer/global.d.ts
  • src/tests/renderer/hooks/useSessionRestoration.test.ts
  • src/renderer/stores/sessionStore.ts
  • src/renderer/hooks/session/useSessionRestoration.ts

📝 Walkthrough

Walkthrough

Added persistent active-session tracking: new IPC handlers and preload APIs for getting/setting activeSessionId, extended session data types, updated renderer store to hydrate/sync active session, and tests adjusted to mock and assert the new IPC endpoints and behaviors.

Changes

Cohort / File(s) Summary
Tests — setup & hooks
src/__tests__/setup.ts, src/__tests__/renderer/hooks/useSessionRestoration.test.ts
Expanded window.maestro.sessions test mock with getActiveSessionId and setActiveSessionId; added a test that verifies restored activeSessionId is read from IPC and applied to the store.
Tests — IPC handler expectations
src/__tests__/main/ipc/handlers/persistence.test.ts
Updated handler registration expectations to include sessions:getActiveSessionId and sessions:setActiveSessionId; added cases asserting getter/setter behavior against the sessions store mock.
IPC & Preload
src/main/ipc/handlers/persistence.ts, src/main/preload/settings.ts
Registered IPC handlers sessions:getActiveSessionId / sessions:setActiveSessionId and exposed corresponding preload bridge methods getActiveSessionId() and setActiveSessionId(id).
Types / Global API
src/main/stores/types.ts, src/renderer/global.d.ts
Added optional activeSessionId?: string to SessionsData; extended MaestroAPI.sessions with getActiveSessionId(): Promise<string> and setActiveSessionId(id: string): Promise<void>.
Renderer — restoration & store
src/renderer/hooks/session/useSessionRestoration.ts, src/renderer/stores/sessionStore.ts
Session restoration now reads persisted activeSessionId via IPC and hydrates or falls back-and-persists; store gained hydrateActiveSessionId (local-only) and setActiveSessionId now updates state and fire-and-forgets persistence via IPC.

Sequence Diagram(s)

sequenceDiagram
    participant Renderer as Renderer (React)
    participant Hook as useSessionRestoration Hook
    participant IPC as Preload / IPC
    participant Persist as Persistence Store
    participant Store as Zustand Store

    Renderer->>Hook: mount -> restore sessions
    Hook->>IPC: window.maestro.sessions.getAll()
    IPC->>Persist: ipc -> sessions:getAll
    Persist-->>IPC: list of sessions
    IPC-->>Hook: sessions[]
    Hook->>IPC: window.maestro.sessions.getActiveSessionId()
    IPC->>Persist: ipc -> sessions:getActiveSessionId
    Persist-->>IPC: activeSessionId | ''
    IPC-->>Hook: activeSessionId
    alt activeSessionId matches restored sessions
        Hook->>Store: hydrateActiveSessionId(activeSessionId)
    else stale or empty
        Hook->>Store: setActiveSessionId(fallbackId)
        Store->>IPC: window.maestro.sessions.setActiveSessionId(fallbackId)
        IPC->>Persist: ipc -> sessions:setActiveSessionId(fallbackId)
    end
Loading
sequenceDiagram
    participant User as User Action
    participant Store as Zustand Store
    participant IPC as Preload / IPC
    participant Persist as Persistence Store

    User->>Store: setActiveSessionId(newId)
    Store->>Store: update activeSessionId, reset cyclePosition
    Store->>IPC: window.maestro.sessions.setActiveSessionId(newId) (fire-and-forget)
    IPC->>Persist: ipc -> sessions:setActiveSessionId(newId)
    Persist-->>IPC: void
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 In burrows deep where sessions hide,

I hop and nudge the active side,
I fetch, I set, through pipes I dart,
Now sessions remember every heart,
Hooray — persistence keeps us smart! 🥕

🚥 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 title clearly and specifically describes the main change: persisting and restoring the active session ID on application launch.
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.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 3, 2026

Greptile Summary

This PR adds persistence for the active session ID so Maestro reopens on the last-used session instead of always defaulting to session [0]. The activeSessionId field is appended to maestro-sessions.json, two new IPC handlers (sessions:getActiveSessionId / sessions:setActiveSessionId) are wired up end-to-end, and the session restoration hook reads the saved ID on startup before falling back to the first session.

Key changes:

  • src/main/stores/types.tsSessionsData gets an optional activeSessionId field (backwards-compatible)
  • src/main/ipc/handlers/persistence.ts — two new IPC handlers read/write activeSessionId via sessionsStore
  • src/main/preload/settings.ts + src/renderer/global.d.ts — preload bridge and type declarations updated
  • src/renderer/stores/sessionStore.ts — both setActiveSessionId and setActiveSessionIdInternal now fire window.maestro?.sessions?.setActiveSessionId() after updating state
  • src/renderer/hooks/session/useSessionRestoration.ts — startup restoration reads the persisted ID and falls back to restoredSessions[0] when not found

Issues found:

  • setActiveSessionIdInternal calls window.maestro?.sessions?.setActiveSessionId() inside the Zustand set((s) => {...}) callback, which should be a pure state-producer function. The IPC side effect should be moved outside the set() call.
  • Because setActiveSessionIdInternal is the code path for keyboard session cycling (Cmd+J/K), this change produces an unthrottled IPC write to disk on every cycle step. Only setActiveSessionId (explicit user intent) should persist; consider skipping persistence in the internal cycling path or debouncing it.
  • Tests for useSessionRestoration always mock getActiveSessionId returning '' — there is no test covering the success path where a valid saved ID is actually restored.

Confidence Score: 3/5

Functionally correct for the happy path, but two issues in sessionStore.ts should be addressed before merging: a side effect inside a Zustand set callback and unthrottled disk writes on every Cmd+J/K cycle step.

The IPC plumbing (new handlers, preload bridge, type declarations) is clean and correct. The startup restoration logic in useSessionRestoration.ts works as intended. The deduction comes from sessionStore.ts: (1) setActiveSessionIdInternal places an IPC call inside the Zustand set() callback, violating the pure-function contract, and (2) because that function drives keyboard cycling, every Cmd+J/K keystroke now triggers a disk write with no throttling. Neither blocks the feature from working, but both are real defects that should be fixed.

src/renderer/stores/sessionStore.ts — setActiveSessionIdInternal side-effect placement and unthrottled persistence during cycling

Important Files Changed

Filename Overview
src/renderer/stores/sessionStore.ts setActiveSessionIdInternal now fires an IPC call inside the Zustand set() callback (a side-effect inside a pure state-producer) and persists on every Cmd+J/K cycle step, causing unthrottled disk writes
src/renderer/hooks/session/useSessionRestoration.ts Correctly reads persisted activeSessionId on startup and falls back to first session; setActiveSessionId is called once during restoration which also persists the (possibly fallback) ID
src/main/ipc/handlers/persistence.ts Two new IPC handlers added (sessions:getActiveSessionId, sessions:setActiveSessionId) using the existing sessionsStore; implementation is clean and consistent with other handlers
src/main/preload/settings.ts createSessionsApi extended with getActiveSessionId and setActiveSessionId wrappers; straightforward IPC bridge, no issues
src/main/stores/types.ts activeSessionId?: string added to SessionsData interface as optional field; backwards-compatible with existing JSON files that don't yet contain the key
src/renderer/global.d.ts MaestroAPI sessions interface updated with getActiveSessionId and setActiveSessionId type declarations; consistent with preload implementation
src/tests/main/ipc/handlers/persistence.test.ts Two new IPC channel names added to the registered-channels assertion; minimal but correct
src/tests/renderer/hooks/useSessionRestoration.test.ts Mock setup updated; however getActiveSessionId is always mocked to return '' — no test covers the case where a valid saved ID is restored
src/tests/setup.ts Global mock extended with getActiveSessionId and setActiveSessionId stubs; straightforward test setup update

Sequence Diagram

sequenceDiagram
    participant R as Renderer (useSessionRestoration)
    participant S as sessionStore (Zustand)
    participant P as Preload Bridge
    participant M as Main Process (persistence.ts)
    participant D as maestro-sessions.json

    Note over R,D: App startup — restore active session
    R->>M: sessions:getAll()
    M->>D: sessionsStore.get('sessions', [])
    D-->>M: StoredSession[]
    M-->>R: savedSessions[]
    R->>R: restoreSession() x N
    R->>M: sessions:getActiveSessionId()
    M->>D: sessionsStore.get('activeSessionId', '')
    D-->>M: savedId (string)
    M-->>R: savedId
    R->>R: targetId = savedId found? savedId : sessions[0].id
    R->>S: setActiveSessionId(targetId)
    S->>S: set({ activeSessionId, cyclePosition: -1 })
    S->>P: setActiveSessionId(targetId)
    P->>M: sessions:setActiveSessionId(targetId)
    M->>D: sessionsStore.set('activeSessionId', targetId)

    Note over R,D: User switches session (explicit click / Cmd+J/K)
    R->>S: setActiveSessionId(id) or setActiveSessionIdInternal(id)
    S->>S: set({ activeSessionId: id })
    S->>P: setActiveSessionId(id)
    P->>M: sessions:setActiveSessionId(id)
    M->>D: sessionsStore.set('activeSessionId', id)
Loading

Reviews (1): Last reviewed commit: "feat: Persist and restore active session..." | Re-trigger Greptile

Comment on lines +206 to +212
setActiveSessionIdInternal: (v) => {
set((s) => {
const newId = resolve(v, s.activeSessionId);
window.maestro?.sessions?.setActiveSessionId(newId);
return { activeSessionId: newId };
});
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Side effect inside Zustand set callback

setActiveSessionIdInternal performs an IPC call (window.maestro?.sessions?.setActiveSessionId) inside the Zustand set((s) => {...}) callback. Zustand expects this function to be a pure state producer — it should only compute and return the next state. Running side effects here can lead to double-invocations in strict/dev modes and makes the intent ambiguous.

setActiveSessionId (the non-internal variant) correctly places the IPC call after the set(...) call. The same pattern should be applied here:

setActiveSessionIdInternal: (v) => {
	let newId: string | undefined;
	set((s) => {
		newId = resolve(v, s.activeSessionId);
		return { activeSessionId: newId };
	});
	if (newId !== undefined) {
		window.maestro?.sessions?.setActiveSessionId(newId);
	}
},

Comment on lines +206 to +212
setActiveSessionIdInternal: (v) => {
set((s) => {
const newId = resolve(v, s.activeSessionId);
window.maestro?.sessions?.setActiveSessionId(newId);
return { activeSessionId: newId };
});
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Unthrottled disk writes during Cmd+J/K session cycling

setActiveSessionIdInternal is the code path used for keyboard-based session cycling (Cmd+J/K). With this change, every cycle step now fires an IPC write to disk. A user holding Cmd+J to scroll through 10 sessions produces 10 writes in rapid succession.

setActiveSessionId (the explicit user-intent path) is the right place for persistence. Consider either:

  1. Skipping persistence in setActiveSessionIdInternal entirely — only persist on explicit setActiveSessionId calls (keyboard cycling is transient intent, not a committed "last open session").
  2. Debouncing the IPC call so only the final resting session ID is written.

Option 1 is simpler and more semantically correct: only write when the user deliberately switches sessions, not while they are mid-cycle.

Comment on lines +434 to 441
const savedActiveSessionId = await window.maestro.sessions.getActiveSessionId();
const targetSessionId =
savedActiveSessionId && restoredSessions.find((s) => s.id === savedActiveSessionId)
? savedActiveSessionId
: restoredSessions[0]?.id;
if (targetSessionId) {
setActiveSessionId(targetSessionId);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 setActiveSessionId persists during initial load, potentially overwriting a valid saved ID

When the saved activeSessionId isn't found in the restored sessions (e.g., that session was deleted), the code falls back to restoredSessions[0]?.id. setActiveSessionId is then called, which immediately fires window.maestro?.sessions?.setActiveSessionId(id) and overwrites the persisted ID with the fallback.

This is correct behavior for the deletion case, but it also means that if sessions haven't fully migrated yet (unlikely but possible), the fallback ID is permanently stored before the next app run can attempt a re-match. A clarifying comment here would help future maintainers understand this intentional overwrite.

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

🧹 Nitpick comments (4)
src/renderer/hooks/session/useSessionRestoration.ts (1)

433-441: Minor redundant IPC write when restoring the same active session ID.

When savedActiveSessionId is valid, calling setActiveSessionId(targetSessionId) triggers a write back to IPC (via the store action's side effect), even though we just read that same value. This is harmless but slightly inefficient.

If you want to avoid the redundant write, you could use a store-internal setter that skips the IPC call when the value hasn't changed, or conditionally call it only when the ID differs from savedActiveSessionId.

♻️ Optional: skip redundant IPC write
 				const targetSessionId =
 					savedActiveSessionId && restoredSessions.find((s) => s.id === savedActiveSessionId)
 						? savedActiveSessionId
 						: restoredSessions[0]?.id;
 				if (targetSessionId) {
-					setActiveSessionId(targetSessionId);
+					// Only persist if we're changing from the saved value (avoid redundant IPC write)
+					if (targetSessionId !== savedActiveSessionId) {
+						setActiveSessionId(targetSessionId);
+					} else {
+						// Just update store state without IPC round-trip
+						useSessionStore.setState({ activeSessionId: targetSessionId, cyclePosition: -1 });
+					}
 				}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/session/useSessionRestoration.ts` around lines 433 - 441,
The restore flow in useSessionRestoration.ts reads savedActiveSessionId via
window.maestro.sessions.getActiveSessionId() then unconditionally calls
setActiveSessionId(targetSessionId), causing a redundant IPC write when the
value is unchanged; change the logic to only call
setActiveSessionId(targetSessionId) when targetSessionId !==
savedActiveSessionId (or use a store-internal setter that skips IPC when
unchanged) so that setActiveSessionId is skipped if the active ID is already the
saved value.
src/__tests__/renderer/hooks/useSessionRestoration.test.ts (1)

137-141: Test mocks are correctly updated.

The mock setup aligns with the global test setup. Consider adding a test case that verifies getActiveSessionId is called during session restoration and that its value is used when valid, to ensure the new IPC integration is covered.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/renderer/hooks/useSessionRestoration.test.ts` around lines 137
- 141, Add a test in useSessionRestoration.test.ts that asserts the hook calls
the mocked IPC method getActiveSessionId and uses its resolved value when it's
non-empty: set (window as any).maestro.sessions.getActiveSessionId to return a
valid session id, spy on that mock to ensure it was invoked during restoration,
and verify the hook selects/sets that session as active (e.g., by asserting
setActiveSessionId or the hook's resulting state). Target the
useSessionRestoration behavior and the mocks getActiveSessionId and
setActiveSessionId so the new IPC integration is covered.
src/__tests__/main/ipc/handlers/persistence.test.ts (1)

144-145: Consider adding unit tests for the new handlers' behavior.

The registration test is updated correctly, but there are no tests verifying the actual sessions:getActiveSessionId and sessions:setActiveSessionId handler logic (e.g., default value when unset, persistence after set). Given these handlers are simple get/set operations, this is a minor gap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/ipc/handlers/persistence.test.ts` around lines 144 - 145,
Add unit tests that exercise the actual handler logic for
'sessions:getActiveSessionId' and 'sessions:setActiveSessionId': write one test
asserting that invoking or calling the 'sessions:getActiveSessionId' handler
returns the expected default (e.g., null/undefined) when nothing has been set,
then write a test that calls 'sessions:setActiveSessionId' with a sample ID and
asserts a subsequent 'sessions:getActiveSessionId' returns that same ID to
verify persistence; place these tests alongside the existing registration test
and use the same invocation helper (e.g., ipc.invoke or the registered handler
function) and assertions pattern used in the file.
src/renderer/stores/sessionStore.ts (1)

201-204: Keep restore hydration separate from user-driven persistence.

useSessionRestoration already reads the saved id from IPC and then calls this setter (src/renderer/hooks/session/useSessionRestoration.ts:435-445), so startup now does a guaranteed read→write round-trip. It also means an invalid saved id gets rewritten to the fallback first session on launch. A small non-persisting hydrate path would keep restore semantics separate from user-initiated changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/stores/sessionStore.ts` around lines 201 - 204, The setter
setActiveSessionId writes to persistent IPC
(window.maestro?.sessions?.setActiveSessionId) and is being called by
useSessionRestoration during startup, causing an undesired read→write round-trip
and overwriting invalid saved ids; create a separate non-persisting hydrate
path: add a new store action (e.g., hydrateActiveSessionId or
setActiveSessionIdLocal) that only updates the local state (activeSessionId and
cyclePosition) without calling window.maestro, leave setActiveSessionId for
user-driven changes that must persist, and update useSessionRestoration to call
the new non-persisting action instead of setActiveSessionId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/stores/sessionStore.ts`:
- Line 203: The IPC call window.maestro.sessions.setActiveSessionId returns a
Promise and is being invoked without await or error handling (both where it's
called directly and inside the Zustand setter), so move the async persistence
outside the synchronous set((s) => { ... }) callback: call and await
window.maestro.sessions.setActiveSessionId(id) (or handle errors with try/catch)
before calling setActiveSessionIdInternal (or, alternatively, call
setActiveSessionIdInternal only after the await succeeds), and ensure any
invocation in other places also either awaits the promise or catches/reports
errors to avoid unhandled rejections.

---

Nitpick comments:
In `@src/__tests__/main/ipc/handlers/persistence.test.ts`:
- Around line 144-145: Add unit tests that exercise the actual handler logic for
'sessions:getActiveSessionId' and 'sessions:setActiveSessionId': write one test
asserting that invoking or calling the 'sessions:getActiveSessionId' handler
returns the expected default (e.g., null/undefined) when nothing has been set,
then write a test that calls 'sessions:setActiveSessionId' with a sample ID and
asserts a subsequent 'sessions:getActiveSessionId' returns that same ID to
verify persistence; place these tests alongside the existing registration test
and use the same invocation helper (e.g., ipc.invoke or the registered handler
function) and assertions pattern used in the file.

In `@src/__tests__/renderer/hooks/useSessionRestoration.test.ts`:
- Around line 137-141: Add a test in useSessionRestoration.test.ts that asserts
the hook calls the mocked IPC method getActiveSessionId and uses its resolved
value when it's non-empty: set (window as
any).maestro.sessions.getActiveSessionId to return a valid session id, spy on
that mock to ensure it was invoked during restoration, and verify the hook
selects/sets that session as active (e.g., by asserting setActiveSessionId or
the hook's resulting state). Target the useSessionRestoration behavior and the
mocks getActiveSessionId and setActiveSessionId so the new IPC integration is
covered.

In `@src/renderer/hooks/session/useSessionRestoration.ts`:
- Around line 433-441: The restore flow in useSessionRestoration.ts reads
savedActiveSessionId via window.maestro.sessions.getActiveSessionId() then
unconditionally calls setActiveSessionId(targetSessionId), causing a redundant
IPC write when the value is unchanged; change the logic to only call
setActiveSessionId(targetSessionId) when targetSessionId !==
savedActiveSessionId (or use a store-internal setter that skips IPC when
unchanged) so that setActiveSessionId is skipped if the active ID is already the
saved value.

In `@src/renderer/stores/sessionStore.ts`:
- Around line 201-204: The setter setActiveSessionId writes to persistent IPC
(window.maestro?.sessions?.setActiveSessionId) and is being called by
useSessionRestoration during startup, causing an undesired read→write round-trip
and overwriting invalid saved ids; create a separate non-persisting hydrate
path: add a new store action (e.g., hydrateActiveSessionId or
setActiveSessionIdLocal) that only updates the local state (activeSessionId and
cyclePosition) without calling window.maestro, leave setActiveSessionId for
user-driven changes that must persist, and update useSessionRestoration to call
the new non-persisting action instead of setActiveSessionId.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4a02d284-2f65-456d-96d0-eca0ac34e733

📥 Commits

Reviewing files that changed from the base of the PR and between 0cd7698 and e5d3580.

📒 Files selected for processing (9)
  • src/__tests__/main/ipc/handlers/persistence.test.ts
  • src/__tests__/renderer/hooks/useSessionRestoration.test.ts
  • src/__tests__/setup.ts
  • src/main/ipc/handlers/persistence.ts
  • src/main/preload/settings.ts
  • src/main/stores/types.ts
  • src/renderer/global.d.ts
  • src/renderer/hooks/session/useSessionRestoration.ts
  • src/renderer/stores/sessionStore.ts

setActiveSessionId: (id) => set({ activeSessionId: id, cyclePosition: -1 }),
setActiveSessionId: (id) => {
set({ activeSessionId: id, cyclePosition: -1 });
window.maestro?.sessions?.setActiveSessionId(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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 'getActiveSessionId|setActiveSessionId|ipcRenderer\.invoke|ipcMain\.handle' \
  src/renderer/global.d.ts \
  src/main/preload/settings.ts \
  src/main/ipc/handlers/persistence.ts

Repository: RunMaestro/Maestro

Length of output: 7348


🏁 Script executed:

cat -n src/renderer/stores/sessionStore.ts | sed -n '195,220p'

Repository: RunMaestro/Maestro

Length of output: 876


Add explicit await or error handling to these IPC calls, especially at line 209.

window.maestro.sessions.setActiveSessionId() returns Promise<void> (via ipcRenderer.invoke), but both lines 203 and 209 drop the promise without handling it. This creates unhandled rejection risk if persistence fails.

Line 209 is more problematic: the IPC call sits inside a Zustand state setter callback (set((s) => { ... })), which should be synchronous. Any async operation here will not block state updates and will cause the renderer to reflect the new activeSessionId before the main process confirms persistence. Move the IPC call outside the setter and await it before calling setActiveSessionIdInternal, or use a separate effect hook to handle persistence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/stores/sessionStore.ts` at line 203, The IPC call
window.maestro.sessions.setActiveSessionId returns a Promise and is being
invoked without await or error handling (both where it's called directly and
inside the Zustand setter), so move the async persistence outside the
synchronous set((s) => { ... }) callback: call and await
window.maestro.sessions.setActiveSessionId(id) (or handle errors with try/catch)
before calling setActiveSessionIdInternal (or, alternatively, call
setActiveSessionIdInternal only after the await succeeds), and ensure any
invocation in other places also either awaits the promise or catches/reports
errors to avoid unhandled rejections.

@scriptease scriptease force-pushed the persist-activesessionid branch from e5d3580 to 5249abb Compare April 3, 2026 10:57
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