From b88b664dbbeecefd4bde21042d5f8f3ad5a88546 Mon Sep 17 00:00:00 2001 From: prod-blip Date: Fri, 10 Apr 2026 16:20:26 +0530 Subject: [PATCH] fix(core): derive activity from runtime probe, not terminal status (#1081) Previously, sessions with terminal statuses (merged, done, killed, etc.) had their activity unconditionally set to "exited" without checking if the agent process was actually still running. This caused the dashboard to show incorrect activity state for sessions where the PR was merged but the agent was still alive in tmux. Now the runtime liveness check runs first for all sessions, and activity is derived from the actual process state via getActivityState(). Terminal status no longer forces activity to "exited". Co-Authored-By: Claude Opus 4.5 --- packages/core/src/session-manager.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/core/src/session-manager.ts b/packages/core/src/session-manager.ts index 1d9765b63..782641fef 100644 --- a/packages/core/src/session-manager.ts +++ b/packages/core/src/session-manager.ts @@ -880,13 +880,9 @@ export function createSessionManager(deps: SessionManagerDeps): OpenCodeSessionM plugins: ReturnType, handleFromMetadata: boolean, ): Promise { - // Skip all subprocess/IO work for sessions already known to be terminal. - if (TERMINAL_SESSION_STATUSES.has(session.status)) { - session.activity = "exited"; - return; - } - - // Check runtime liveness — but only if the handle came from metadata. + // Check runtime liveness first — regardless of session status. + // This fixes #1081: terminal statuses (merged, done, etc.) should not force + // activity to "exited" if the agent process is still alive. // Fabricated handles (constructed as fallback for external sessions) should // NOT override status to "killed" — we don't know if the session ever had // a tmux session, and we'd clobber meaningful statuses like "pr_open". @@ -894,7 +890,11 @@ export function createSessionManager(deps: SessionManagerDeps): OpenCodeSessionM try { const alive = await plugins.runtime.isAlive(session.runtimeHandle); if (!alive) { - session.status = "killed"; + // Process is confirmed dead — set activity to exited. + // Only update status to "killed" if not already in a terminal state. + if (!TERMINAL_SESSION_STATUSES.has(session.status)) { + session.status = "killed"; + } session.activity = "exited"; return; } @@ -903,10 +903,12 @@ export function createSessionManager(deps: SessionManagerDeps): OpenCodeSessionM } } - // Detect activity independently of runtime handle. + // Detect activity independently of runtime handle and session status. // Activity detection reads JSONL files on disk — it only needs workspacePath, // not a runtime handle. Gating on runtimeHandle caused sessions created by // external scripts (which don't store runtimeHandle) to always show "unknown". + // This now runs for ALL sessions, including terminal statuses, so a merged + // session with a live agent shows accurate activity (ready/idle/waiting_input). if (plugins.agent) { try { const detected = await plugins.agent.getActivityState(session, config.readyThresholdMs);