Cancel Process
Overview
The CancelProcessInstanceCommand terminates a running, waiting, or error process instance. The process is immediately stopped and marked as cancelled. This action cannot be undone.
Irreversible Action
Cancelling a process permanently terminates it. The process cannot be resumed or restarted from its current position. Consider pausing instead if you need to temporarily stop execution.
API Endpoint
POST /api/core/cmd
Headers
Content-Type: application/json
Authorization: Bearer {access_token}
X-Tenant-ID: {tenant_id}
Request Structure
{
"cmd": "CancelProcessInstanceCommand",
"data": {
"instanceGuid": "abc-123-def-456",
"reason": "Customer withdrew application",
"cancelledBy": "user@example.com"
}
}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
instanceGuid | string | Yes | The unique identifier of the process instance to cancel |
reason | string | No | Reason for cancellation (for audit trail) |
cancelledBy | string | No | User or system that initiated cancellation |
When to Use
✅ Good Use Cases
- User-Initiated Cancellation: Customer withdrew loan application
- Business Rule Violation: Detected fraud or policy violation
- Timeout: Process exceeded maximum allowed time
- External Event: Related resource no longer exists
- Error Recovery: Process in unrecoverable error state
- Testing: Clean up test process instances
❌ When NOT to Use
- Temporary Pause: Use pause/resume instead (if available)
- Task Failure: Consider retry or skip instead
- User Action Needed: Use UserTask to wait for input
- Normal Completion: Let process complete naturally
Sample Requests
1. Customer Withdrawal
{
"cmd": "CancelProcessInstanceCommand",
"data": {
"instanceGuid": "loan-app-2024-001",
"reason": "Customer called to withdraw loan application - no longer needs funds",
"cancelledBy": "support@bank.com"
}
}
2. Policy Violation
{
"cmd": "CancelProcessInstanceCommand",
"data": {
"instanceGuid": "account-opening-456",
"reason": "Applicant found on sanctions list during KYC verification",
"cancelledBy": "compliance-system"
}
}
3. Timeout
{
"cmd": "CancelProcessInstanceCommand",
"data": {
"instanceGuid": "kyc-process-789",
"reason": "Process exceeded maximum 30-day limit without completion",
"cancelledBy": "timeout-monitor"
}
}
4. Error Recovery
{
"cmd": "CancelProcessInstanceCommand",
"data": {
"instanceGuid": "payment-process-321",
"reason": "Unrecoverable error in payment gateway integration - manual processing required",
"cancelledBy": "admin@bank.com"
}
}
Response Structure
Successfully Cancelled
{
"isSuccessful": true,
"message": "Process instance cancelled successfully.",
"statusCode": "00",
"data": {
"instanceGuid": "loan-app-2024-001",
"status": "cancelled",
"cancelledAt": "2024-12-19T10:45:00Z",
"cancellationDetails": {
"reason": "Customer called to withdraw loan application - no longer needs funds",
"cancelledBy": "support@bank.com",
"previousStatus": "waiting",
"currentActivityId": "Task_ApprovalDecision"
},
"state": {
"variables": {
"loanId": 789,
"customerId": 456,
"loanAmount": 50000,
"status": "cancelled"
},
"completedTasks": [
"StartEvent",
"Task_ValidateCustomer",
"Task_CreditCheck",
"Task_AssessRisk"
],
"currentNode": "Task_ApprovalDecision",
"duration": 1200000,
"startedAt": "2024-12-19T10:25:00Z",
"endedAt": "2024-12-19T10:45:00Z"
}
}
}
Cancelled from Error State
{
"isSuccessful": true,
"message": "Process instance cancelled successfully.",
"statusCode": "00",
"data": {
"instanceGuid": "payment-process-321",
"status": "cancelled",
"cancelledAt": "2024-12-19T10:50:00Z",
"cancellationDetails": {
"reason": "Unrecoverable error in payment gateway integration",
"cancelledBy": "admin@bank.com",
"previousStatus": "error",
"errorDetails": {
"taskId": "Task_ProcessPayment",
"taskName": "Process Payment",
"errorMessage": "Payment gateway returned 500 Internal Server Error after 5 retries"
}
},
"state": {
"variables": {
"paymentId": 12345,
"amount": 10000,
"retryCount": 5
},
"completedTasks": [
"StartEvent",
"Task_ValidatePayment"
]
}
}
}
Error Responses
Process Not Found
{
"isSuccessful": false,
"message": "Process instance 'invalid-guid' not found.",
"statusCode": "99",
"data": null
}
Process Already Completed
{
"isSuccessful": false,
"message": "Cannot cancel process instance - already completed.",
"statusCode": "99",
"data": {
"instanceGuid": "loan-app-2024-001",
"status": "completed",
"completedAt": "2024-12-19T09:00:00Z"
}
}
Process Already Cancelled
{
"isSuccessful": false,
"message": "Process instance is already cancelled.",
"statusCode": "99",
"data": {
"instanceGuid": "loan-app-2024-001",
"status": "cancelled",
"cancelledAt": "2024-12-19T08:00:00Z"
}
}
Process Lifecycle with Cancellation
Use Cases
1. User-Initiated Cancellation
async function handleCustomerWithdrawal(applicationId, customerId, reason) {
// Get the process instance for this application
const processInstance = await getProcessInstanceByApplication(applicationId);
if (!processInstance) {
throw new Error(`No active process found for application ${applicationId}`);
}
// Verify customer owns this application
const state = await getProcessState(processInstance.guid);
if (state.data.state.variables.customerId !== customerId) {
throw new Error("Unauthorized: Customer does not own this application");
}
// Cancel the process
const response = await cancelProcess({
instanceGuid: processInstance.guid,
reason: `Customer withdrawal: ${reason}`,
cancelledBy: `customer:${customerId}`
});
if (response.isSuccessful) {
// Update application status
await updateApplicationStatus(applicationId, 'WITHDRAWN');
// Send confirmation email
await sendEmail({
to: state.data.state.variables.customerEmail,
subject: "Application Withdrawn",
body: `Your loan application has been successfully withdrawn.`
});
// Log for audit
await auditLog.record({
action: "CUSTOMER_WITHDRAWAL",
applicationId,
customerId,
reason,
timestamp: new Date()
});
}
return response;
}
2. Automatic Timeout Monitoring
class ProcessTimeoutMonitor {
constructor(maxDuration = 30 * 24 * 60 * 60 * 1000) { // 30 days
this.maxDuration = maxDuration;
}
async monitorProcesses() {
const runningProcesses = await this.getActiveProcesses();
for (const process of runningProcesses) {
await this.checkTimeout(process);
}
}
async checkTimeout(process) {
const startTime = new Date(process.startedAt);
const now = new Date();
const duration = now - startTime;
if (duration > this.maxDuration) {
console.warn(`Process ${process.instanceGuid} exceeded timeout`);
await cancelProcess({
instanceGuid: process.instanceGuid,
reason: `Process exceeded maximum duration of ${this.maxDuration}ms`,
cancelledBy: "timeout-monitor"
});
// Alert operations team
await this.alertOperations({
severity: "WARNING",
message: `Process ${process.instanceGuid} cancelled due to timeout`,
process
});
}
}
async getActiveProcesses() {
// Query for running or waiting processes
return await db.query(`
SELECT instanceGuid, startedAt, status
FROM ProcessInstances
WHERE status IN ('running', 'waiting')
`);
}
async alertOperations(alert) {
// Send to operations monitoring system
console.log("ALERT:", alert);
}
}
// Run monitor every hour
const monitor = new ProcessTimeoutMonitor();
setInterval(() => monitor.monitorProcesses(), 60 * 60 * 1000);
3. Cancellation with Cleanup
async function cancelWithCleanup(instanceGuid, reason, cancelledBy) {
// Get current state before cancelling
const state = await getProcessState(instanceGuid);
const variables = state.data.state.variables;
// Cancel the process
const response = await cancelProcess({
instanceGuid,
reason,
cancelledBy
});
if (response.isSuccessful) {
// Perform cleanup operations
await cleanupAfterCancellation(variables);
// Send notifications
await notifyStakeholders(variables, reason);
// Update related records
await updateRelatedRecords(variables, 'CANCELLED');
}
return response;
}
async function cleanupAfterCancellation(variables) {
const cleanupTasks = [];
// Release reserved resources
if (variables.reservationId) {
cleanupTasks.push(
releaseReservation(variables.reservationId)
);
}
// Cancel scheduled tasks
if (variables.scheduledJobId) {
cleanupTasks.push(
cancelScheduledJob(variables.scheduledJobId)
);
}
// Cleanup temporary files
if (variables.tempFileIds) {
cleanupTasks.push(
deleteTempFiles(variables.tempFileIds)
);
}
// Execute all cleanup tasks
await Promise.all(cleanupTasks);
}
async function notifyStakeholders(variables, reason) {
// Notify customer
if (variables.customerEmail) {
await sendEmail({
to: variables.customerEmail,
subject: "Application Cancelled",
body: `Your application has been cancelled. Reason: ${reason}`
});
}
// Notify assigned staff
if (variables.assignedTo) {
await sendNotification({
userId: variables.assignedTo,
message: `Process ${variables.applicationId} cancelled: ${reason}`
});
}
}
4. Bulk Cancellation
async function bulkCancelProcesses(criteria, reason) {
// Find processes matching criteria
const processes = await findProcesses(criteria);
console.log(`Found ${processes.length} processes to cancel`);
const results = {
successful: [],
failed: []
};
for (const process of processes) {
try {
const response = await cancelProcess({
instanceGuid: process.instanceGuid,
reason: `Bulk cancellation: ${reason}`,
cancelledBy: "system-admin"
});
if (response.isSuccessful) {
results.successful.push(process.instanceGuid);
} else {
results.failed.push({
guid: process.instanceGuid,
error: response.message
});
}
} catch (error) {
results.failed.push({
guid: process.instanceGuid,
error: error.message
});
}
}
console.log(`Cancelled ${results.successful.length} processes`);
console.log(`Failed to cancel ${results.failed.length} processes`);
return results;
}
// Example: Cancel all processes for a specific customer
await bulkCancelProcesses(
{ customerId: 12345 },
"Customer account closed"
);
// Example: Cancel all processes older than 60 days
await bulkCancelProcesses(
{ olderThan: Date.now() - (60 * 24 * 60 * 60 * 1000) },
"Exceeded retention period"
);
Best Practices
1. Always Provide a Reason
Good:
{
"reason": "Customer called support at 10:15 AM to withdraw application. Stated they found better rates with another lender. Ref: TICKET-2024-5678",
"cancelledBy": "support@bank.com"
}
Bad:
{
"reason": "Cancelled"
}
2. Verify Before Cancelling
async function safeCancelProcess(instanceGuid, reason) {
// 1. Verify process exists and is cancellable
const state = await getProcessState(instanceGuid);
if (!state.isSuccessful) {
throw new Error("Process not found");
}
const status = state.data.status;
if (status === "completed") {
throw new Error("Cannot cancel completed process");
}
if (status === "cancelled") {
throw new Error("Process already cancelled");
}
// 2. Check for critical operations in progress
const currentTask = state.data.currentActivityId;
const criticalTasks = [
"Task_ProcessPayment",
"Task_TransferFunds",
"Task_CreateAccount"
];
if (criticalTasks.includes(currentTask)) {
console.warn(`Cancelling during critical task: ${currentTask}`);
// Maybe require additional confirmation
}
// 3. Cancel with full context
return await cancelProcess({
instanceGuid,
reason,
cancelledBy: getCurrentUser()
});
}
3. Implement Confirmation for User-Initiated Cancellations
async function cancelWithConfirmation(instanceGuid, reason) {
// Get process details
const state = await getProcessState(instanceGuid);
const variables = state.data.state.variables;
// Show confirmation dialog
const confirmed = await showConfirmation({
title: "Cancel Process?",
message: `Are you sure you want to cancel this ${variables.processType}?`,
details: [
`Application ID: ${variables.applicationId}`,
`Current Stage: ${state.data.currentActivityId}`,
`Started: ${state.data.startedAt}`,
`Completed Tasks: ${state.data.state.completedTasks.length}`
],
warning: "This action cannot be undone.",
confirmText: "Yes, Cancel Process",
cancelText: "No, Go Back"
});
if (!confirmed) {
return { cancelled: false, reason: "User declined confirmation" };
}
return await cancelProcess({
instanceGuid,
reason,
cancelledBy: getCurrentUser()
});
}
4. Log All Cancellations
async function cancelWithAudit(instanceGuid, reason, cancelledBy) {
const startTime = Date.now();
// Get state before cancellation
const beforeState = await getProcessState(instanceGuid);
// Perform cancellation
const response = await cancelProcess({
instanceGuid,
reason,
cancelledBy
});
// Log to audit system
await auditLog.record({
action: "CANCEL_PROCESS",
instanceGuid,
reason,
cancelledBy,
timestamp: new Date(),
duration: Date.now() - startTime,
beforeState: {
status: beforeState.data.status,
currentTask: beforeState.data.currentActivityId,
completedTasks: beforeState.data.state?.completedTasks?.length || 0
},
success: response.isSuccessful
});
// Alert if high-value process
if (beforeState.data.state?.variables?.loanAmount > 100000) {
await alertManagement({
severity: "HIGH",
message: `High-value loan process cancelled: ${instanceGuid}`,
amount: beforeState.data.state.variables.loanAmount,
reason
});
}
return response;
}
Related Commands
- Get Process State - Check process status before cancelling
- Skip Failed Task - Alternative to cancellation for failed tasks
- Retry Failed Task - Try recovery before cancelling
Notes
- Cancellation is permanent and cannot be undone
- Cancelled processes cannot be resumed or restarted
- All process state is preserved for audit purposes
- Can cancel processes in any state (running, waiting, error)
- Cannot cancel already completed processes
- Cannot cancel already cancelled processes
- Reason and cancelledBy fields are optional but recommended for audit trails
- Consider cleanup operations after cancellation (release resources, notify stakeholders)
- Implement confirmation for user-initiated cancellations
- Monitor cancellation patterns to identify process issues
- Use bulk cancellation carefully - ensure proper error handling