diff --git a/src/web/public/app.js b/src/web/public/app.js index 35b10fb5..8c9a297f 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -2105,8 +2105,9 @@ class CodemanApp { // Track working directory for path normalization in Project Insights this.currentSessionWorkingDir = session?.workingDir || null; - if (session && session.pid === null && session.status === 'idle') { - // This is a restored session - attach to the existing screen/shell + if (session && session.pid === null && !session._ended) { + // Session has no PTY attached — either restored after server restart + // or detached for some other reason. Re-attach regardless of status. try { const endpoint = session.mode === 'shell' ? `/api/sessions/${sessionId}/shell` diff --git a/src/web/server.ts b/src/web/server.ts index 5395d04b..e93df334 100644 --- a/src/web/server.ts +++ b/src/web/server.ts @@ -1766,15 +1766,28 @@ export class WebServer extends EventEmitter { this.sessions.set(session.id, session); await this.setupSessionListeners(session); - this.persistSessionState(session); - // Mark it as restored (not started yet - user needs to attach) - getLifecycleLog().log({ - event: 'recovered', - sessionId: session.id, - name: session.name, - }); - console.log(`[Server] Restored session ${session.id} from mux ${muxSession.muxName}`); + // Auto-attach PTY to the surviving tmux session immediately. + // This ensures ALL sessions resume capturing output right away, + // not just the one the client happens to select first. + try { + await session.startInteractive(); + getLifecycleLog().log({ + event: 'recovered', + sessionId: session.id, + name: session.name, + }); + console.log(`[Server] Restored and attached session ${session.id} from mux ${muxSession.muxName}`); + } catch (attachErr) { + console.error(`[Server] Failed to attach session ${session.id}, keeping as detached:`, attachErr); + getLifecycleLog().log({ + event: 'recovered', + sessionId: session.id, + name: session.name, + }); + } + + this.persistSessionState(session); } }