{"record":{"id":"3295d43e46d873ac","repo":"paperclipai/paperclip","slug":"action-task-closed","errorCode":"action_task_closed","errorMessage":"Task is closed","messagePattern":"Task is closed","errorType":"http","errorClass":"ToolGatewayHttpError","httpStatus":409,"severity":"error","filePath":"server/src/services/tool-gateway.ts","lineNumber":6893,"sourceCode":"      }\n      if (actionRequest.status === \"approved\") {\n        await reflectToolActionInteractionLifecycle({ actionRequestId: actionRequest.id, status: \"approved\" });\n        if (!isTestOriginInvocation(invocation) && signedPayload.executionOnApprove === true) {\n          try {\n            await executeApprovedAgentInvocation({ actionRequest, invocation });\n          } catch {\n            // The execution outcome is persisted on the invocation/request and\n            // reflected onto the accepted interaction for the continuation wake.\n          }\n          const [settled] = await db.select().from(toolActionRequests).where(eq(toolActionRequests.id, actionRequest.id)).limit(1);\n          return actionRequestResolution(settled ?? actionRequest);\n        }\n        return actionRequest;\n      }\n      if (actionRequest.expiresAt && actionRequest.expiresAt <= new Date()) throw new ToolGatewayHttpError(409, \"Tool review has expired\", \"action_expired\");\n      if (!isTestOriginInvocation(invocation) && signedPayload.executionOnApprove === true) {\n        const [issue] = await db.select().from(issues).where(and(eq(issues.id, invocation.issueId!), eq(issues.companyId, input.companyId))).limit(1);\n        if (!issue || issue.status === \"done\" || issue.status === \"cancelled\") throw new ToolGatewayHttpError(409, \"Task is closed\", \"action_task_closed\");\n        const session: ToolGatewaySession = { id: `review:${actionRequest.id}`, token: \"\", companyId: input.companyId, agentId: invocation.agentId, runId: invocation.runId, issueId: issue.id, projectId: issue.projectId, createdAt: new Date(), expiresAt: new Date(Date.now() + DEFAULT_SESSION_TTL_MS) };\n        await restoreApprovedActionIdentity(session, signedPayload.identityContextId);\n        const tool = await findToolForSession(session, invocation.toolName);\n        if (!approvalSnapshotsMatch(signedPayload.approvalSnapshot, await connectedRemoteApprovalSnapshot(session, tool))) throw new ToolGatewayHttpError(409, \"Tool definition or connection changed; request a new review\", \"approved_tool_target_changed\");\n        const access = await policyService.decide(policyInputForTool({ session, tool, parameters: signedPayload.arguments }));\n        if (!access.allowed && access.decision !== \"require_approval\") throw new ToolGatewayHttpError(403, access.explanation, access.reasonCode);\n      }\n      const updated = await commitToolActionReview(db, { ...input, decision: \"approved\" });\n      await reflectToolActionInteractionLifecycle({ actionRequestId: updated.id, status: \"approved\" });\n      // A test-tab ask-first request has no agent run to carry out the parked\n      // call, so approving it is what runs it. Execute against the signed\n      // arguments and record the result on the invocation for the live panel.\n      if (isTestOriginInvocation(invocation)) {\n        await runApprovedTestInvocation(\n          { ...invocation, approvalState: \"approved\" },\n          signedPayload.arguments,\n          updated.id,\n        );","sourceCodeStart":6875,"sourceCodeEnd":6911,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/tool-gateway.ts#L6875-L6911","documentation":"For approved tool reviews that carry executionOnApprove, the gateway verifies the linked issue still exists in the company and is not in a terminal state ('done' or 'cancelled') before executing. A closed task means the approved action no longer has a live context, so it refuses with 409.","triggerScenarios":"Resolving an approved tool action whose invocation.issueId points to an issue with status 'done' or 'cancelled', or an issue id that no longer exists in that company.","commonSituations":"Human approves a parked tool review long after the underlying task finished; issue cancelled while approval pending; cross-company issue id mismatch after a data fix.","solutions":["Re-request the tool review from the active run/issue, then approve that new request","Reopen the issue (or create a successor issue) and re-run the approval flow","Verify invocation.issueId is correct and belongs to the same company"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const issue = await getIssue(invocation.issueId);\nif (!issue || ['done','cancelled'].includes(issue.status)) throw new Error('Issue closed; re-request tool review');","typeGuard":null,"tryCatchPattern":"catch (e) { if (e.code === 'action_task_closed') { await reissueToolActionRequest(currentIssueId); } else throw e; }","preventionTips":["Approve pending tool reviews before closing or cancelling issues","Auto-cancel open tool reviews when their issue reaches a terminal state","Check issue status in the review UI before approving"],"tags":["lifecycle","approval","issue"],"backgroundTag":"invalid-state-transition","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}