fix(tmux-subagent): retry stale sweep if first attempt throws

Oracle flagged: staleSweepCompleted was set to true BEFORE
sweepStaleOmoAgentSessions() ran, so any throw from the first
invocation would permanently disable stale cleanup for the rest
of the process lifetime.

Fix:
- Move staleSweepCompleted=true into the try-block success branch.
- Add staleSweepInProgress guard so concurrent onSessionCreated calls
  do not invoke sweep twice in parallel (sweep is idempotent, but the
  guard prevents doubled log noise).
- finally{} clears the inProgress flag regardless of outcome.
- cleanup() resets both flags.

Two new tests cover: retry after a thrown first attempt, and single
invocation when subsequent spawns follow a successful first sweep.
This commit is contained in:
YeonGyu-Kim
2026-04-18 20:45:21 +09:00
parent 859d67f41e
commit 913fac05f5
2 changed files with 48 additions and 1 deletions
@@ -1932,6 +1932,47 @@ describe('TmuxSessionManager', () => {
expect(mockKillTmuxSessionIfExists).toHaveBeenCalledTimes(0)
})
test('#given sweepStaleOmoAgentSessions throws on first onSessionCreated #when second onSessionCreated fires #then sweep is retried instead of skipped forever', async () => {
// given
mockSweepStaleOmoAgentSessions.mockClear()
mockSweepStaleOmoAgentSessions.mockImplementationOnce(async () => {
throw new Error('simulated sweep failure')
})
mockIsInsideTmux.mockReturnValue(true)
const { TmuxSessionManager } = await import('./manager')
const manager = new TmuxSessionManager(createMockContext(), createTmuxConfig({
enabled: true,
isolation: 'session',
}), mockTmuxDeps)
// when
await manager.onSessionCreated(createSessionCreatedEvent('ses_first', 'ses_parent', 'First'))
await manager.onSessionCreated(createSessionCreatedEvent('ses_second', 'ses_parent', 'Second'))
// then
expect(mockSweepStaleOmoAgentSessions).toHaveBeenCalledTimes(2)
})
test('#given sweepStaleOmoAgentSessions succeeds #when additional onSessionCreated events fire in same process #then sweep runs exactly once', async () => {
// given
mockSweepStaleOmoAgentSessions.mockClear()
mockSweepStaleOmoAgentSessions.mockImplementation(async () => 0)
mockIsInsideTmux.mockReturnValue(true)
const { TmuxSessionManager } = await import('./manager')
const manager = new TmuxSessionManager(createMockContext(), createTmuxConfig({
enabled: true,
isolation: 'session',
}), mockTmuxDeps)
// when
await manager.onSessionCreated(createSessionCreatedEvent('ses_a', 'ses_parent', 'A'))
await manager.onSessionCreated(createSessionCreatedEvent('ses_b', 'ses_parent', 'B'))
await manager.onSessionCreated(createSessionCreatedEvent('ses_c', 'ses_parent', 'C'))
// then
expect(mockSweepStaleOmoAgentSessions).toHaveBeenCalledTimes(1)
})
test('#given killTmuxSessionIfExists throws #when cleanup runs #then cleanup still completes without throwing', async () => {
// given
mockKillTmuxSessionIfExists.mockClear()