Skip to content

fix: clear stale agentSessionId on session_not_found loop#544

Open
openasocket wants to merge 1 commit intoRunMaestro:0.16.0-RCfrom
openasocket:fix/session-not-found-loop
Open

fix: clear stale agentSessionId on session_not_found loop#544
openasocket wants to merge 1 commit intoRunMaestro:0.16.0-RCfrom
openasocket:fix/session-not-found-loop

Conversation

@openasocket
Copy link
Contributor

@openasocket openasocket commented Mar 9, 2026

Summary

  • When a Claude session expires or is deleted, the stale agentSessionId was never cleared — every subsequent prompt tried --resume with the dead ID, received session_not_found, and looped endlessly
  • Now clears agentSessionId at both tab and session level on session_not_found errors, so the next prompt starts a fresh session automatically

Test plan

  • Unit test added: verifies agentSessionId is cleared at both tab and session level on session_not_found
  • End-to-end: let a Claude session expire, send a new prompt — should recover without manual intervention

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Session identifiers are now properly cleared when a session-not-found error occurs, ensuring correct state management and proper cleanup for subsequent interactions.

…ite loop

When a Claude session expires or is deleted, subsequent prompts would
try --resume with the stale session ID, receive session_not_found, and
repeat endlessly. Now clears agentSessionId at both the tab and session
level so the next prompt starts a fresh session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7926922d-de2c-421a-9fb3-d72245fe4538

📥 Commits

Reviewing files that changed from the base of the PR and between c7abfdf and 1954d48.

📒 Files selected for processing (2)
  • src/__tests__/renderer/hooks/useAgentListeners.test.ts
  • src/renderer/hooks/agent/useAgentListeners.ts

📝 Walkthrough

Walkthrough

The PR adds error handling for session_not_found conditions in the agent listeners hook. When this error type occurs, the code now clears the agentSessionId at both the tab-level and session-level to prevent attempts to resume a deleted session. A corresponding test verifies this behavior.

Changes

Cohort / File(s) Summary
Session Not Found Error Handling
src/renderer/hooks/agent/useAgentListeners.ts, src/__tests__/renderer/hooks/useAgentListeners.test.ts
Implements and tests error handling for session_not_found conditions. Clears per-tab agentSessionId and global session-level agentSessionId when this error type occurs, preventing stale session resumption and potential infinite loops. Implementation adds conditional logic to reset session identifiers; test adds 33-line test case to verify dual-level clearing behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 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 describes the main change: clearing stale agentSessionId on session_not_found errors to prevent an infinite loop. It directly summarizes the primary bug fix.
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
  • Post copyable unit tests in a comment

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

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR fixes an infinite-loop bug where a stale agentSessionId was never cleared after a Claude session expired, causing every subsequent prompt to attempt --resume with the dead ID, receive session_not_found, and repeat endlessly. The fix adds an early-return branch in onAgentError that clears agentSessionId at both the tab and session level when a session_not_found error is received — so the next prompt starts a fresh session automatically.

Key changes:

  • In useAgentListeners.ts: when isSessionNotFound is true, an early return now clears the tab-level agentSessionId (via spread) and the session-level agentSessionId. Critically, the session is not placed into 'error' state, which allows the exit handler (which fires immediately after the error event) to process the execution queue normally with a fresh session — consistent with how the group-chat path already handles session_not_found.
  • In useAgentListeners.test.ts: a focused unit test verifies that both levels of agentSessionId are cleared when the error type is session_not_found.

Confidence Score: 4/5

  • Safe to merge — the fix is narrowly scoped, well-tested, and aligns with the existing group-chat recovery pattern.
  • The root cause (stale agentSessionId never cleared) is clearly identified and the fix is minimal. The approach of not setting 'error' state is consistent with the group-chat session_not_found handling pattern already in the codebase. One minor concern is that the session-level agentSessionId is cleared unconditionally for all tabs, which could be overly broad in a multi-tab scenario, but is unlikely to cause issues today.
  • No files require special attention — changes are isolated to the session_not_found branch of onAgentError.

Important Files Changed

Filename Overview
src/renderer/hooks/agent/useAgentListeners.ts Adds early return for session_not_found errors that clears agentSessionId at both tab and session level, breaking the infinite --resume loop. Session state/error fields are left for the exit handler to clean up, which aligns with the group-chat comment noting "exit-listener will handle recovery". Minor concern: session-level agentSessionId is cleared unconditionally regardless of which tab triggered the error.
src/tests/renderer/hooks/useAgentListeners.test.ts New unit test correctly exercises the session_not_found path and asserts both tab-level and session-level agentSessionId are cleared. Does not assert that the session state is not left stuck in 'busy' after the fix.

Sequence Diagram

sequenceDiagram
    participant UI as UI / Prompt
    participant Hook as useAgentListeners
    participant Store as SessionStore
    participant Agent as Claude Agent

    Note over UI,Agent: Before fix — infinite loop
    UI->>Agent: --resume stale-session-abc123
    Agent-->>Hook: onAgentError(session_not_found)
    Hook->>Store: set state='error' (agentSessionId unchanged)
    UI->>Agent: --resume stale-session-abc123 (loop)

    Note over UI,Agent: After fix — recovery
    UI->>Agent: --resume stale-session-abc123
    Agent-->>Hook: onAgentError(session_not_found)
    Hook->>Store: clear tab.agentSessionId + session.agentSessionId
    Agent-->>Hook: onAgentExit
    Hook->>Store: set state='idle' (exit handler)
    UI->>Agent: (no --resume flag) start fresh session
Loading

Comments Outside Diff (1)

  1. src/renderer/hooks/agent/useAgentListeners.ts, line 1223-1230 (link)

    Session-level agentSessionId cleared unconditionally for any tab's error

    The session-level agentSessionId is always cleared when isSessionNotFound is true, even when the error originated from a specific tab (tabIdFromSession is set). In a multi-tab session where different tabs might hold distinct agent sessions, this unconditionally wipes the session-wide agentSessionId even if other tabs are still active.

    The tab-level clearing (above) is correctly scoped to targetTab. Consider applying the same conditionality to the session-level clear — either only clearing it when no specific tabIdFromSession was resolved, or when the erroring tab's agentSessionId matches the session-level one:

    // Only clear session-level agentSessionId if it matches the stale one
    // (or no specific tab is targeted)
    ...(isSessionNotFound && (!tabIdFromSession || s.agentSessionId === targetTab?.agentSessionId)
      ? { agentSessionId: undefined }
      : {}),

    This may be a non-issue today if the session-level agentSessionId is always in sync with the active tab, but the current logic is worth documenting or scoping more tightly.

Last reviewed commit: 1954d48

Comment on lines +694 to +725
it('clears agentSessionId on session_not_found so next prompt starts fresh', () => {
const deps = createMockDeps();
const tab = createMockTab({
id: 'tab-1',
agentSessionId: 'stale-session-abc123',
});
const session = createMockSession({
id: 'sess-1',
state: 'busy',
aiTabs: [tab],
activeTabId: 'tab-1',
agentSessionId: 'stale-session-abc123',
});
useSessionStore.setState({
sessions: [session],
activeSessionId: 'sess-1',
});

renderHook(() => useAgentListeners(deps));

onAgentErrorHandler?.('sess-1-ai-tab-1', {
...baseError,
type: 'session_not_found',
});

const updated = useSessionStore.getState().sessions.find((s) => s.id === 'sess-1');
const updatedTab = updated?.aiTabs.find((t) => t.id === 'tab-1');
// Tab-level agentSessionId must be cleared
expect(updatedTab?.agentSessionId).toBeUndefined();
// Session-level agentSessionId must also be cleared
expect(updated?.agentSessionId).toBeUndefined();
});
Copy link

Choose a reason for hiding this comment

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

Test doesn't assert session state remains usable after the error

The test verifies agentSessionId is cleared (the primary goal), but the session was created with state: 'busy' and is never checked afterward. If the exit handler doesn't fire in a test environment, the session could remain 'busy', leaving the UI input disabled.

While the production exit event should always follow the error event (transitioning state to 'idle'), it would strengthen the test to also assert the session state is not left in an unusable state, or to document that the state cleanup is delegated to the exit handler:

// Verify agentSessionId is cleared
expect(updatedTab?.agentSessionId).toBeUndefined();
expect(updated?.agentSessionId).toBeUndefined();

// State cleanup is handled by onAgentExit; verify it is NOT set to 'error'
// (which would block the execution queue from dequeuing the next item)
expect(updated?.state).not.toBe('error');

@openasocket openasocket changed the base branch from main to 0.16.0-RC March 9, 2026 04:37
@pedramamini pedramamini force-pushed the 0.16.0-RC branch 4 times, most recently from 1883595 to 24f4bce Compare March 11, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant