diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 833db6306ac..d618afea52c 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -2497,6 +2497,9 @@ refs and files are verified reproducible. Shared project clones remain intact. Normal authorized reopening recreates a missing source and checks out the saved commit, even after upstream advances. Inaccessible upstream produces an explicit retryable restoration failure. It never substitutes the default branch. +Reopening failures reject the consuming request with HTTP 409 before saving a +comment or changing its task. The response preserves the restoration message, so +the composer keeps an editable draft and permits retry after access is restored. Both enable switches default off. Rollback disables further expiration while leaving saved recipes and restoration support intact. Recovery archives, runner diff --git a/server/src/__tests__/issue-closed-workspace-routes.test.ts b/server/src/__tests__/issue-closed-workspace-routes.test.ts index 2ab80dadce7..d1875ff5144 100644 --- a/server/src/__tests__/issue-closed-workspace-routes.test.ts +++ b/server/src/__tests__/issue-closed-workspace-routes.test.ts @@ -318,7 +318,7 @@ describe("closed isolated workspace issue routes", () => { expect(mockIssueService.addComment).not.toHaveBeenCalled(); }); - it("returns 503 and blocks the checkout when the rebuild fails", async () => { + it("returns 409 and blocks the checkout when the rebuild fails", async () => { mockExecutionWorkspaceService.reopenClosedIsolatedExecutionWorkspaceForIssue.mockResolvedValue({ ok: false, code: "rebuild_failed", @@ -332,10 +332,29 @@ describe("closed isolated workspace issue routes", () => { expectedStatuses: ["todo", "backlog", "blocked"], }); - expect(res.status).toBe(503); + expect(res.status).toBe(409); expect(mockIssueService.checkout).not.toHaveBeenCalled(); }); + it.each(["comment", "update"])("returns a definite %s rejection when the saved commit cannot be restored", async (operation) => { + const message = "The saved workspace commit could not be restored. Check repository access and retry."; + mockExecutionWorkspaceService.reopenClosedIsolatedExecutionWorkspaceForIssue.mockResolvedValue({ + ok: false, + code: "rebuild_failed", + message, + }); + + const app = request(createApp()); + const res = operation === "comment" + ? await app.post(`/api/issues/${issueId}/comments`).send({ body: "continue" }) + : await app.patch(`/api/issues/${issueId}`).send({ comment: "continue" }); + + expect(res.status).toBe(409); + expect(res.body.error).toBe(message); + expect(mockIssueService.addComment).not.toHaveBeenCalled(); + expect(mockIssueService.update).not.toHaveBeenCalled(); + }); + it("does not reopen the workspace when a checkout fails the run-id gate", async () => { // An agent checkout without a run id is rejected before the reopen runs. The // reopen must not rebuild and republish the workspace, or the still-terminal diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index b89f2d6c139..eacaea637c2 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -7780,10 +7780,7 @@ export function issueRoutes( "This issue is linked to a closed workspace that cannot be reopened.", }); } else { - res.status(503).json({ - error: - "Could not reopen the workspace for this issue. Please try again.", - }); + res.status(409).json({ error: result.message }); } return null; }