Merge pull request #4231 from code-yeongyu/fix/post-4228-test-and-session-gone

Fix post-4228 test and session-gone follow-ups
This commit is contained in:
YeonGyu-Kim
2026-05-21 13:18:10 +09:00
committed by GitHub
5 changed files with 50 additions and 17 deletions
@@ -1,7 +1,7 @@
import { describe, expect, mock, test } from "bun:test"
import type { OpencodeClient } from "./opencode-client"
import { verifySessionExists } from "./session-existence"
import { checkSessionExistence, verifySessionExists } from "./session-existence"
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
describe("verifySessionExists", () => {
@@ -24,4 +24,20 @@ describe("verifySessionExists", () => {
query: { directory: "/project/root" },
})
})
test("classifies transient lookup errors as unknown", async () => {
const get = mock(async () => ({
error: { message: "Network timeout", status: 500 },
data: undefined,
}))
const client = unsafeTestValue<OpencodeClient>({
session: {
get,
},
})
const result = await checkSessionExistence(client, "session-123")
expect(result).toBe("unknown")
})
})
@@ -1,6 +1,7 @@
import type { OpencodeClient } from "./opencode-client"
export const MIN_SESSION_GONE_POLLS = 3
export type SessionExistenceStatus = "exists" | "missing" | "unknown"
function extractErrorMessage(error: unknown): string | undefined {
if (typeof error === "string") {
@@ -35,11 +36,11 @@ function isSessionNotFoundError(error: unknown): boolean {
return message.includes("not found") || message.includes("missing")
}
export async function verifySessionExists(
export async function checkSessionExistence(
client: OpencodeClient,
sessionID: string,
directory?: string
): Promise<boolean> {
): Promise<SessionExistenceStatus> {
try {
const response = await client.session.get({
path: { id: sessionID },
@@ -47,11 +48,19 @@ export async function verifySessionExists(
})
if (response.error !== undefined && response.error !== null) {
return !isSessionNotFoundError(response.error)
return isSessionNotFoundError(response.error) ? "missing" : "unknown"
}
return response.data != null
return response.data != null ? "exists" : "missing"
} catch (error) {
return !isSessionNotFoundError(error)
return isSessionNotFoundError(error) ? "missing" : "unknown"
}
}
export async function verifySessionExists(
client: OpencodeClient,
sessionID: string,
directory?: string
): Promise<boolean> {
return await checkSessionExistence(client, sessionID, directory) !== "missing"
}
@@ -472,7 +472,7 @@ describe("checkAndInterruptStaleTasks", () => {
expect(mockClient.session.get).toHaveBeenCalledWith({ path: { id: "ses-1" } })
})
it("should NOT cancel task when session.get returns a transient error response", async () => {
it("should NOT cancel or reset missed polls when session.get returns a transient error response", async () => {
//#given - repeated missing polls but lookup failed with a retryable transport error
const task = createRunningTask({
startedAt: new Date(Date.now() - 300_000),
@@ -500,7 +500,7 @@ describe("checkAndInterruptStaleTasks", () => {
//#then
expect(task.status).toBe("running")
expect(task.consecutiveMissedPolls).toBe(0)
expect(task.consecutiveMissedPolls).toBe(3)
expect(mockClient.session.get).toHaveBeenCalledWith({ path: { id: "ses-1" } })
})
+15 -7
View File
@@ -16,7 +16,7 @@ import {
} from "./constants"
import { abortWithTimeout } from "./abort-with-timeout"
import { removeTaskToastTracking } from "./remove-task-toast-tracking"
import { MIN_SESSION_GONE_POLLS, verifySessionExists } from "./session-existence"
import { checkSessionExistence, MIN_SESSION_GONE_POLLS } from "./session-existence"
import { isActiveSessionStatus } from "./session-status-classifier"
import { getSessionActivityFromClient, type SessionActivityResolver } from "./session-activity"
@@ -178,9 +178,13 @@ export async function checkAndInterruptStaleTasks(args: {
if (activityRefresh.type === "activity" && now - activityRefresh.activityTime <= effectiveTimeout) continue
}
if (sessionGone && await verifySessionExists(client, sessionID, directory)) {
task.consecutiveMissedPolls = 0
continue
if (sessionGone) {
const existence = await checkSessionExistence(client, sessionID, directory)
if (existence === "exists") {
task.consecutiveMissedPolls = 0
continue
}
if (existence === "unknown") continue
}
const staleMinutes = Math.round(runtime / 60000)
@@ -228,9 +232,13 @@ export async function checkAndInterruptStaleTasks(args: {
if (task.status !== "running") continue
if (sessionGone && await verifySessionExists(client, sessionID, directory)) {
task.consecutiveMissedPolls = 0
continue
if (sessionGone) {
const existence = await checkSessionExistence(client, sessionID, directory)
if (existence === "exists") {
task.consecutiveMissedPolls = 0
continue
}
if (existence === "unknown") continue
}
const staleMinutes = Math.round(timeSinceLastUpdate / 60000)
@@ -231,7 +231,7 @@ describe("remapAgentKeysToDisplayNames", () => {
const result = remapAgentKeysToDisplayNames(agents, overrides)
// then the legacy AGENT_DISPLAY_NAMES value is used
expect(result["Sisyphus - Ultraworker"]).toBeDefined()
expect(result[getAgentListDisplayName("sisyphus")]).toBeDefined()
expect(result["总指挥"]).toBeUndefined()
})
@@ -245,7 +245,7 @@ describe("remapAgentKeysToDisplayNames", () => {
const result = remapAgentKeysToDisplayNames(agents)
// then the legacy AGENT_DISPLAY_NAMES value is used
expect(result["Sisyphus - Ultraworker"]).toBeDefined()
expect(result[getAgentListDisplayName("sisyphus")]).toBeDefined()
})
})
})