paperclipai/paperclip · error · ToolGatewayHttpError

action_task_closed

action_task_closed

Error message

Task is closed

What it means

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.

Source

Thrown at server/src/services/tool-gateway.ts:6893

      }
      if (actionRequest.status === "approved") {
        await reflectToolActionInteractionLifecycle({ actionRequestId: actionRequest.id, status: "approved" });
        if (!isTestOriginInvocation(invocation) && signedPayload.executionOnApprove === true) {
          try {
            await executeApprovedAgentInvocation({ actionRequest, invocation });
          } catch {
            // The execution outcome is persisted on the invocation/request and
            // reflected onto the accepted interaction for the continuation wake.
          }
          const [settled] = await db.select().from(toolActionRequests).where(eq(toolActionRequests.id, actionRequest.id)).limit(1);
          return actionRequestResolution(settled ?? actionRequest);
        }
        return actionRequest;
      }
      if (actionRequest.expiresAt && actionRequest.expiresAt <= new Date()) throw new ToolGatewayHttpError(409, "Tool review has expired", "action_expired");
      if (!isTestOriginInvocation(invocation) && signedPayload.executionOnApprove === true) {
        const [issue] = await db.select().from(issues).where(and(eq(issues.id, invocation.issueId!), eq(issues.companyId, input.companyId))).limit(1);
        if (!issue || issue.status === "done" || issue.status === "cancelled") throw new ToolGatewayHttpError(409, "Task is closed", "action_task_closed");
        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) };
        await restoreApprovedActionIdentity(session, signedPayload.identityContextId);
        const tool = await findToolForSession(session, invocation.toolName);
        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");
        const access = await policyService.decide(policyInputForTool({ session, tool, parameters: signedPayload.arguments }));
        if (!access.allowed && access.decision !== "require_approval") throw new ToolGatewayHttpError(403, access.explanation, access.reasonCode);
      }
      const updated = await commitToolActionReview(db, { ...input, decision: "approved" });
      await reflectToolActionInteractionLifecycle({ actionRequestId: updated.id, status: "approved" });
      // A test-tab ask-first request has no agent run to carry out the parked
      // call, so approving it is what runs it. Execute against the signed
      // arguments and record the result on the invocation for the live panel.
      if (isTestOriginInvocation(invocation)) {
        await runApprovedTestInvocation(
          { ...invocation, approvalState: "approved" },
          signedPayload.arguments,
          updated.id,
        );

View on GitHub (pinned to 01ad858492)

Solutions

  1. Re-request the tool review from the active run/issue, then approve that new request
  2. Reopen the issue (or create a successor issue) and re-run the approval flow
  3. Verify invocation.issueId is correct and belongs to the same company
Defensive patterns

Strategy: try-catch

Validate before calling

const issue = await getIssue(invocation.issueId);
if (!issue || ['done','cancelled'].includes(issue.status)) throw new Error('Issue closed; re-request tool review');

Try / catch

catch (e) { if (e.code === 'action_task_closed') { await reissueToolActionRequest(currentIssueId); } else throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/3295d43e46d873ac. Report an issue: GitHub.