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
- 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
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
- 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
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
- provider turn ended with status ${turn.status}
- Plugin UI is not available (status: ${plugin.status})
- Plugin is not ready (current status: ${plugin.status})
- ${message}
- Durable PRP control plane is not listening.
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/3295d43e46d873ac.
Report an issue: GitHub.