SUPPORT.TWILIO.COM END OF LIFE NOTICE: This site, support.twilio.com, is scheduled to go End of Life on February 27, 2024. All Twilio Support content has been migrated to help.twilio.com, where you can continue to find helpful Support articles, API docs, and Twilio blog content, and escalate your issues to our Support team. We encourage you to update your bookmarks and begin using the new site today for all your Twilio Support needs.

Stuck Voice Tasks From TaskRouter

Issue

In rare situations, TaskRouter Tasks can become stuck in a non-terminal state (pending, reserved, or assigned) after the underlying communication channel has already completed. 

Normally, the Task state is driven by the associated channel (for example, when a Voice call ends, the Task automatically enters the wrapping state). During outages or abnormal conditions, TaskRouter and the channel can fall out of sync, requiring manual intervention.

 

Product

Flex

 

Environment

legacy Twilio Console

 

Cause

During outages or abnormal conditions, orchestration between TaskRouter and the underlying communication channel can become misaligned. 

 

Resolution

This can be resolved by updating the stuck Task's assignmentStatus to a terminal state:

  • If the Task was never assigned (pending or reserved): cancel it.
  • If the Task was assigned to an agent (assigned): complete it.

 

How to determine if a voice Task is "stuck"

  1. First confirm that the Task in question is still active in TaskRouter by fetching the Task resource.
curl -X GET "https://taskrouter.twilio.com/v1/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN

If you get a 404 and you know you have a valid Task SID, then the Task has likely already been closed for over 5 minutes. This means the Task is not stuck in TaskRouter. (If you are still seeing Task data in the Flex UI or elsewhere, this can indicate a different "stuck" scenario with the Flex UI.)

  1. Review the Task's assignment_status property to determine if its still open.
  2. If the Task is still open, grab the Call SID from the Task's attributes - look for the call_sid property which is populated by Twilio during orchestration.

Task attributes example:

{"from_country":"US","called":"+1555xxxxxxx","call_sid":"CAb920ad5f119ab57be3fxxxxxxxx","from":"+1678xxxxxxx", "to":"+1555xxxxxxx","direction":"inbound"}
  1. Once you have the Call SID, fetch the Call resource to see its state. Below is an example in CURL:
curl -X GET "https://api.twilio.com/2010-04-01/Accounts/ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/Calls/CAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.json" 
-u ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:your_auth_token

The following are terminal states for Calls: completed, failed, busy, no-answer

If the Call is no longer active, but the Task is still active, the Task can be considered "stuck".

NOTE:  This guidance is intended for Flex environments with default voice orchestration. If your Flex instance has customized orchestration, please ensure this "stuck" Task logic won’t close legitimately active Tasks.

 

How to update Tasks

Tasks can be updated in the following ways:

  • Individually from the Twilio Console > TaskRouter > Workspace > Tasks
  • Individually or in bulk using Twilio's API or helper libraries

 

Solution Examples

Process Bulk Tasks with Node.js

For an example of how to approach a script using Twilio's Node.js helper library, see the following solution which:

  • Fetches open Tasks (pending, reserved, assigned)
  • Filters for only "voice" Tasks
  • Finds the related call via call_sid Task attribute
  • Fetches the Voice call to verify the call's status. 
  • If the call is in a terminal state (completed, failed, busy, or no-answer), it updates the Task accordingly:
    • assignedcompleted
    • pending/reservedcanceled

This approach helps clear stuck Tasks after outages while avoiding active ones. 

 

Dependencies

Install the following npm packages:

  • twilio
  • dotenv

 

Configure

Update with your environment-specific variables:

  1. Set your Twilio credentials (TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) in your .env file.
  2. Set your workspace SID in the script.

 

Code

Save the following code as a file in your Node project:

// Twilio TaskRouter Stuck Task Cleaner
// Fetches open tasks, and closes those with completed calls

// Load environment variables
const dotenv = require('dotenv');
dotenv.config();

// Initialize Twilio Client with auto-retry
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const client = require('twilio')(accountSid, authToken, 
   { autoRetry: true, maxRetries: 3 }
);

// Define workspace SID inline (replace with your actual workspace SID)
const workspaceSid = 'WSxxxxxxxxxxxxxxxxxxxx;

// Define custom reason for task closure for reporting
const voiceReason = 'Orchestration issue: closed stuck voice Task.';

// Define terminal call states
const TERMINAL_CALL_STATES = ["completed", "failed", "busy", "no-answer"];

// Main logic 
(async () => {
    let tasksUpdated = 0;
    let processed = 0;
    let skipped = 0;
    try {
        await new Promise((resolve, reject) => {
            client.taskrouter.v1.workspaces(workspaceSid).tasks.each({
                assignmentStatus: 'pending,reserved,assigned', // fetch only open Tasks
                pageSize: 1000,
                callback: async (task, done) => {
                    processed++;
                    let attributes = {};
                    try {
                        attributes = JSON.parse(task.attributes);
                    } catch {
                        console.log(`Could not parse attributes for task ${task.sid}`);
                        skipped++;
                        done();
                        return;
                    }
                    if (task.taskChannelUniqueName !== 'voice' || !attributes.call_sid) {
                        skipped++;
                        done();
                        return;
                    }
                    try {
                        const call = await client.calls(attributes.call_sid).fetch();
                        if (TERMINAL_CALL_STATES.includes(call.status)) {
                            const status = task.assignmentStatus === 'assigned' ? 'completed' : 'canceled';
                            await client.taskrouter.v1.workspaces(workspaceSid).tasks(task.sid).update({
                                assignmentStatus: status,
                                reason: voiceReason
                            });
                            console.log(`Updated voice task ${task.sid} to ${status}`);
                            tasksUpdated++;
                        }
                    } catch (err) {
                        console.log(`Error processing task ${task.sid}:`, err);
                    }
                    if (processed % 100 === 0) {
                        console.log(`Processed ${processed} tasks so far...`);
                    }
                    done();
                },
                done: error => {
                    if (error) reject(error);
                    else resolve();
                }
            });
        });
        console.log(`Updated ${tasksUpdated} tasks to completed/canceled.`);
        console.log(`Processed ${processed} tasks, skipped ${skipped} tasks.`);
    } catch (err) {
        console.error('Error processing tasks:', err);
        process.exit(1);
    }
})();

Run the script

Run node <YOUR_FILE_NAME>.js> to begin processing Tasks.

 

Additional Information

Have more questions? Submit a request
Powered by Zendesk