fix(background-agent): fail abort on SDK errors
Treat resolved abort responses with a non-null error payload the same as rejected aborts. This prevents stale-timeout cancellation bookkeeping from reporting success when the child session was not actually aborted. Plan: .omo/plans/subagent-timeout-active-output.md
This commit is contained in:
@@ -37,6 +37,22 @@ describe("abortWithTimeout", () => {
|
|||||||
expect(logMock).not.toHaveBeenCalled()
|
expect(logMock).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("#given abort resolves with an SDK error response #when abortWithTimeout runs #then it reports cancellation failure", async () => {
|
||||||
|
// given
|
||||||
|
const error = { message: "session not found" }
|
||||||
|
const abort = mock(async () => ({ error }))
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = await abortWithTimeout(createClient(abort), "session-error-response", 10)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result).toBe(false)
|
||||||
|
expect(logMock).toHaveBeenCalledWith(
|
||||||
|
"[background-agent] Session abort returned an error response:",
|
||||||
|
{ sessionID: "session-error-response", error },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test("#given abort hangs indefinitely #when abortWithTimeout runs #then it logs warning and continues", async () => {
|
test("#given abort hangs indefinitely #when abortWithTimeout runs #then it logs warning and continues", async () => {
|
||||||
// given
|
// given
|
||||||
const abort = mock(() => new Promise<never>(() => {}))
|
const abort = mock(() => new Promise<never>(() => {}))
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { log } from "../../shared"
|
import { log } from "../../shared"
|
||||||
|
import { isRecord } from "../../shared/record-type-guard"
|
||||||
import type { OpencodeClient } from "./opencode-client"
|
import type { OpencodeClient } from "./opencode-client"
|
||||||
|
|
||||||
|
function getAbortResponseError(response: unknown): unknown | undefined {
|
||||||
|
if (!isRecord(response)) return undefined
|
||||||
|
const error = response.error
|
||||||
|
return error === undefined || error === null ? undefined : error
|
||||||
|
}
|
||||||
|
|
||||||
export async function abortWithTimeout(
|
export async function abortWithTimeout(
|
||||||
client: OpencodeClient,
|
client: OpencodeClient,
|
||||||
sessionID: string,
|
sessionID: string,
|
||||||
@@ -10,7 +17,26 @@ export async function abortWithTimeout(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await Promise.race([
|
const result = await Promise.race([
|
||||||
client.session.abort({ path: { id: sessionID } }).then(() => "aborted" as const),
|
client.session.abort({ path: { id: sessionID } }).then(
|
||||||
|
(response) => {
|
||||||
|
const error = getAbortResponseError(response)
|
||||||
|
if (error !== undefined) {
|
||||||
|
log("[background-agent] Session abort returned an error response:", {
|
||||||
|
sessionID,
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
return "failed" as const
|
||||||
|
}
|
||||||
|
return "aborted" as const
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
log("[background-agent] Session abort failed:", {
|
||||||
|
sessionID,
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
return "failed" as const
|
||||||
|
},
|
||||||
|
),
|
||||||
new Promise<"timed_out">((resolve) => {
|
new Promise<"timed_out">((resolve) => {
|
||||||
timeoutHandle = setTimeout(() => {
|
timeoutHandle = setTimeout(() => {
|
||||||
resolve("timed_out")
|
resolve("timed_out")
|
||||||
@@ -26,7 +52,7 @@ export async function abortWithTimeout(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return result === "aborted"
|
||||||
} finally {
|
} finally {
|
||||||
if (timeoutHandle) {
|
if (timeoutHandle) {
|
||||||
clearTimeout(timeoutHandle)
|
clearTimeout(timeoutHandle)
|
||||||
|
|||||||
Reference in New Issue
Block a user