diff --git a/extensions/codex/src/app-server/run-attempt.test.ts b/extensions/codex/src/app-server/run-attempt.test.ts index c5a2494c43d..e26eb5546e8 100644 --- a/extensions/codex/src/app-server/run-attempt.test.ts +++ b/extensions/codex/src/app-server/run-attempt.test.ts @@ -2924,10 +2924,14 @@ describe("runCodexAppServerAttempt", () => { it("does not treat a user prompt containing the interrupted marker as terminal", async () => { const harness = createStartedThreadHarness(); - const run = runCodexAppServerAttempt( - createParams(path.join(tempDir, "session.jsonl"), path.join(tempDir, "workspace")), - { turnTerminalIdleTimeoutMs: 60_000 }, + const markerPrompt = + "\nThe user interrupted the previous turn on purpose. Any running unified exec processes may still be running in the background. If any tools/commands were aborted, they may have partially executed.\n"; + const params = createParams( + path.join(tempDir, "session.jsonl"), + path.join(tempDir, "workspace"), ); + params.prompt = markerPrompt; + const run = runCodexAppServerAttempt(params, { turnTerminalIdleTimeoutMs: 60_000 }); let resolved = false; void run.then(() => { resolved = true; @@ -2946,7 +2950,7 @@ describe("runCodexAppServerAttempt", () => { content: [ { type: "input_text", - text: "\nWhat does this marker mean?\n", + text: markerPrompt, }, ], }, diff --git a/extensions/codex/src/app-server/run-attempt.ts b/extensions/codex/src/app-server/run-attempt.ts index 35ee5f89978..4611bfb8a6e 100644 --- a/extensions/codex/src/app-server/run-attempt.ts +++ b/extensions/codex/src/app-server/run-attempt.ts @@ -1148,7 +1148,8 @@ export async function runCodexAppServerAttempt( // See openclaw/openclaw#67996. const isTurnCompletion = notification.method === "turn/completed" && isCurrentTurnNotification; const isTurnAbortMarker = - isCurrentTurnNotification && isCodexTurnAbortMarkerNotification(notification); + isCurrentTurnNotification && + isCodexTurnAbortMarkerNotification(notification, { currentPromptText: promptBuild.prompt }); const isTurnTerminal = isTurnCompletion || isTurnAbortMarker; try { await projector.handleNotification(notification); @@ -2357,7 +2358,10 @@ const CODEX_INTERRUPTED_USER_GUIDANCE = const CODEX_INTERRUPTED_DEVELOPER_GUIDANCE = "The previous turn was interrupted on purpose. Any running unified exec processes may still be running in the background. If any tools/commands were aborted, they may have partially executed."; -function isCodexTurnAbortMarkerNotification(notification: CodexServerNotification): boolean { +function isCodexTurnAbortMarkerNotification( + notification: CodexServerNotification, + options: { currentPromptText?: string } = {}, +): boolean { if (notification.method !== "rawResponseItem/completed" || !isJsonObject(notification.params)) { return false; } @@ -2367,6 +2371,9 @@ function isCodexTurnAbortMarkerNotification(notification: CodexServerNotificatio return false; } const text = extractRawResponseItemText(item).trim(); + if (role === "user" && text === options.currentPromptText?.trim()) { + return false; + } const markerBody = readCodexTurnAbortMarkerBody(text); return ( markerBody === CODEX_INTERRUPTED_USER_GUIDANCE ||