Merge preserved Grok cleanup failure outcomes

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* codex/instruction-provider-stop-boundary:
  fix(grok): retain provider results across collection and restore failures
This commit is contained in:
DottaandPaperclip committed 2026-09-27 19:42:23 -05:00
commit e822ce4d71
2 files changed
+56 -3

No files matched your search

@@ -868,12 +868,48 @@ describe("grok_local execute", () => {
});
const ctx = await makeCtx("run-collection-reject", await makeTempRoot());
ctx.onProviderStopped = async () => { order.push("collect"); throw collectionError; };
await expect(execute(ctx)).rejects.toBe(collectionError);
const result = await execute(ctx);
expect(result.errorCode).toBe("instruction_collection_failed");
expect(result.resultJson?.instructionCollectionFailure).toBe("collection_failed");
expect(result.resultJson?.workspaceRestoreFailure).toBeUndefined();
expect(order).toEqual(["collect", "restore"]);
expect(stagedDir).not.toBe("");
expect(await pathExists(stagedDir)).toBe(false);
});
it.each([0, 2])("preserves provider output when collection and restore both fail after exit %s", async (exitCode) => {
delete process.env.XAI_API_KEY;
mocks.state.isRemote = true;
await seedHostGrokAuth("{}");
runProcessMock.mockImplementation(async (_run, _target, _command, _args, options) => {
options.onProcessStopped();
return {
...makeSuccessfulRunResult(), exitCode,
stderr: exitCode ? "Model request failed." : "",
stdout: [JSON.stringify({ type: "text", data: "Saved output." }), JSON.stringify({
type: "end", sessionId: "sess-1", requestId: "req-1", stopReason: "EndTurn",
usage: { input_tokens: 4, output_tokens: 9 },
})].join("\n"),
};
});
prepareRuntimeMock.mockImplementationOnce(async () => ({
workspaceRemoteDir: "/remote/workspace",
assetDirs: { home: "/remote/workspace/.paperclip-runtime/grok/home" },
restoreWorkspace: async () => { throw new Error("restore failed"); },
}));
const ctx = await makeCtx("run-collection-and-restore-reject", await makeTempRoot());
ctx.onProviderStopped = async () => { throw new Error("instruction collection failed"); };
const result = await execute(ctx);
expect(result).toMatchObject({
errorCode: "workspace_restore_failed", exitCode, sessionId: "sess-1", summary: "Saved output.",
usage: { inputTokens: 4, outputTokens: 9 },
resultJson: { instructionCollectionFailure: "collection_failed", workspaceRestoreFailure: "restore_failed",
requestId: "req-1", finalResponseRecorded: true, executionBeforeRestore: { exitCode } },
});
expect(result.errorMessage).toContain("Instruction collection failed");
if (exitCode) expect(result.errorMessage).toContain("Model request failed.");
});
it.each(["completed", "failed", "timed_out"])("preserves %s output and removes the staged home when restore fails", async (state) => {
delete process.env.XAI_API_KEY;
mocks.state.isRemote = true;
@@ -730,11 +730,28 @@ async function executeTurn(ctx: AdapterExecutionContext): Promise<AdapterExecuti
try {
return await withWorkspaceRestore(
async () => {
let result: AdapterExecutionResult;
let collectionFailed = false;
const collectionFailureMessage = "Instruction collection failed after provider stop. No instruction save is claimed.";
try {
return await executeTurn();
result = await executeTurn();
} finally {
await providerStop.collectBeforeRestore();
try {
await providerStop.collectBeforeRestore();
} catch {
collectionFailed = true;
await onLog("stderr", `[paperclip] ${collectionFailureMessage}\n`).catch(() => undefined);
}
}
if (!collectionFailed) return result;
const providerFailed = result.timedOut || result.signal || result.errorCode
|| (result.exitCode !== null && result.exitCode !== 0);
return {
...result,
...(!providerFailed ? { errorCode: "instruction_collection_failed" } : {}),
errorMessage: [result.errorMessage, collectionFailureMessage].filter(Boolean).join(" "),
resultJson: { ...result.resultJson, instructionCollectionFailure: "collection_failed" },
};
},
async () => { await restoreRemoteWorkspace?.(); },
);