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()
|
||||
})
|
||||
|
||||
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 () => {
|
||||
// given
|
||||
const abort = mock(() => new Promise<never>(() => {}))
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { log } from "../../shared"
|
||||
import { isRecord } from "../../shared/record-type-guard"
|
||||
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(
|
||||
client: OpencodeClient,
|
||||
sessionID: string,
|
||||
@@ -10,7 +17,26 @@ export async function abortWithTimeout(
|
||||
|
||||
try {
|
||||
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) => {
|
||||
timeoutHandle = setTimeout(() => {
|
||||
resolve("timed_out")
|
||||
@@ -26,7 +52,7 @@ export async function abortWithTimeout(
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return result === "aborted"
|
||||
} finally {
|
||||
if (timeoutHandle) {
|
||||
clearTimeout(timeoutHandle)
|
||||
|
||||
Reference in New Issue
Block a user