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.

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@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/3295d43e46d873ac. Report an issue: GitHub.

Appendix: source

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

          "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,

View on GitHub (pinned to 3f1d897a7c)