From 2ecf3ba783b965f2f09c878e3e70024cc99ce68b Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:56:27 +0200 Subject: [PATCH] Keep an archived agent archived when a send to it fails (#6255) * Keep an archived agent archived when a send to it fails Sending a message to an archived agent unarchives it before loading the session. When loading failed, for example because the agent's working directory was removed, the agent stayed unarchived. The send now restores the original archive timestamp when it fails, matching resume_agent. Closes #6245 * Restore the archive only when the send cannot load the agent Concurrent sends share one load, so a failed load leaves no session for the restore to close. A failure after the load no longer re-archives an agent another send may already be running. --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- .../server/src/server/agent/agent-prompt.ts | 24 ++++++++++++++----- .../src/server/daemon-client.e2e.test.ts | 23 ++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/server/src/server/agent/agent-prompt.ts b/packages/server/src/server/agent/agent-prompt.ts index 41e2d52014..0bcd46636a 100644 --- a/packages/server/src/server/agent/agent-prompt.ts +++ b/packages/server/src/server/agent/agent-prompt.ts @@ -309,18 +309,30 @@ export async function sendPromptToAgent( const unarchive = params.unarchive ?? true; const record = await params.agentStorage.get(params.agentId); + let archivedAtToRestore: string | null = null; if (record?.archivedAt) { if (!unarchive) { return { disposition: "turn_started" }; } - await unarchiveAgentState(params.agentStorage, params.agentManager, params.agentId); + if (await unarchiveAgentState(params.agentStorage, params.agentManager, params.agentId)) { + archivedAtToRestore = record.archivedAt; + } } - await ensureAgentLoaded(params.agentId, { - agentManager: params.agentManager, - agentStorage: params.agentStorage, - logger: params.logger, - }); + try { + await ensureAgentLoaded(params.agentId, { + agentManager: params.agentManager, + agentStorage: params.agentStorage, + logger: params.logger, + }); + } catch (error) { + // A send that could not load the agent leaves it where it was: still archived. + // Concurrent sends share this load, so none of them holds a live session. + if (archivedAtToRestore) { + await params.agentManager.archiveSnapshot(params.agentId, archivedAtToRestore); + } + throw error; + } if (params.sessionMode) { await params.agentManager.setAgentMode(params.agentId, params.sessionMode); diff --git a/packages/server/src/server/daemon-client.e2e.test.ts b/packages/server/src/server/daemon-client.e2e.test.ts index f429cb672c..0bc3310b41 100644 --- a/packages/server/src/server/daemon-client.e2e.test.ts +++ b/packages/server/src/server/daemon-client.e2e.test.ts @@ -831,6 +831,29 @@ test("send_agent_message auto-unarchives archived agents", async () => { } }, 180000); +test("send_agent_message leaves an archived agent archived when its directory is gone", async () => { + const cwd = tmpCwd(); + try { + const created = await ctx.client.createAgent({ + config: { + ...getFullAccessConfig("codex"), + cwd, + }, + }); + const archived = await ctx.client.archiveAgent(created.id); + rmSync(cwd, { recursive: true, force: true }); + + await expect(ctx.client.sendMessage(created.id, "hello")).rejects.toThrow( + "Working directory does not exist", + ); + + const afterSend = await ctx.client.fetchAgent({ agentId: created.id }); + expect(afterSend?.agent.archivedAt).toBe(archived.archivedAt); + } finally { + rmSync(cwd, { recursive: true, force: true }); + } +}, 30000); + test("refresh_agent auto-unarchives archived agents", async () => { const cwd = tmpCwd(); try {