External task is created but polling finds 0 tasks

I have a very bizarre predicament. I’m using Camunda 7.18 and have followed the External Tasks documentation as well as using the GitHub example for implementing it in NodeJS. I have two processes, playSession and updateTier, both of which contain external tasks and both of which have their corresponding code handled by the same NodeJS process worker (i.e. topic registration and polling).

For some reason the updateTier process gets stuck at the very first external task, updateTier_RetrieveMemberDetails. I can’t spot any difference in how this task is configured as compared to any of the external tasks in playSession, yet playSession works perfectly fine whereas updateTier does not. I’ve verified that the external task is correctly getting created, it’s not being locked nor suspended, and if I manually make a fetch request I can assign a worker to it just fine. However, the external tasks simply don’t get picked up otherwise.

The logs show that all of the topics have been correctly subscribed to and I’ve checked countless times to verify that the topic names correctly match up. The polling is set to a 5 second interval with a 10 second timeout for the sake of testing, so it’s not an issue of infrequent polling either. When I run the playSession process the polling says that it has found 1 task and correctly executes upon it. When I run the updateTier process the polling only ever says that it has found 0 tasks, meaning that despite the tasks assuredly existing they are never seen for some reason.

This issue is going to drive me crazy so any help would be greatly appreciated! I have attached the bpmn files for both processes. I haven’t included the topic handlers as currently the handlers for the updateTier process literally just print to console and mark the task as complete, but even that is not being reached (since polling does not find any updateTier tasks to begin with).
playSession.bpmn (39.1 KB)
updateTier.bpmn (5.4 KB)

Hi @mjawhari

Could you please attach the worker implementation? or post the code here?

Certainly! Sorry in the initial message I tried to attach it but it wouldn’t let me add .js files.

topics/index.js:

'use strict';

// common modules
import { Client } from 'camunda-external-task-client-js';

// custom modules
import { interceptor } from '#utilities/camundaApi.js';


// topic registration
const topics = [
  'playSession_RetrieveUserDetails',
  'playSession_CreateBucket',
  'playSession_GenerateSignedUrls',
  'playSession_CreateInstance',
  'playSession_RetrieveCreateInstanceDetails',
  'playSession_RetrieveSessionStartDetails',
  'playSession_RetrieveSessionDetails',
  'playSession_StopInstance',
  'playSession_DeleteInstance',
  'playSession_RetrieveInstanceDetails',
  'updateTier_RetrieveMemberDetails',
  'updateTier_UpdateKeycloakUser'
];

export default async (logger) => {
  const client = new Client({
    baseUrl: process.env.CAMUNDA_ENGINE,
    workerId: process.env.CAMUNDA_WORKER,
    interceptors: interceptor,
    lockDuration: parseInt(process.env.CAMUNDA_WORKER_LOCKTIME),
    asyncResponseTimeout: 10000,
    interval: 5000,
  });

  // configure logging
  client.on('subscribe', (topic) => {
    logger.info(`Subscribed to topic: ${topic}`);
  })
  client.on('unsubscribe', (topic) => {
    logger.info(`Unsubscribed from topic: ${topic}`);
  })
  client.on('poll:start', () => {
    logger.info('Polling started.');
  })
  client.on('poll:stop', () => {
    logger.info('Polling stopped.');
  })
  client.on('poll:success', (tasks) => {
    logger.info(`Polling successful. Found ${tasks.length} tasks.`);
  })
  client.on('poll:error', (error) => {
    logger.error(`Could not poll successfully: ${error}`);
  })
  client.on('complete:success', (task) => {
    logger.info(`Completed task ${task.id}`);
  })
  client.on('complete:error', (task, error) => {
    logger.error(`Unable to complete task #{task.id}; ${error}`);
  })
  client.on('handleFailure:success', (task) => {
    logger.info(`Reported failure for task ${task.id}`);
  })
  client.on('handleFailure:error', (task, error) => {
    logger.error(`Could not report failure for task ${task.id}; ${error}`);
  })
  client.on('handleBpmnError:success', (task) => {
    logger.info(`Reported BPMN Error for task ${task.id}`);
  })
  client.on('handleBpmnError:error', (task, error) => {
    logger.error(`Could not report BPMN error for task ${task.id}; ${error}`);
  })
  client.on('extendLock:success', (task) => {
    logger.info(`Extended lock for task ${task.id}`);
  })
  client.on('extendLock:error', (task, error) => {
    logger.error(`Could not extend lock for task ${task.id}; ${error}`);
  })
  client.on('unlock:success', (task) => {
    logger.info(`Unlocked task ${task.id}`);
  })
  client.on('unlock:error', (task, error) => {
    logger.error(`Could not unlock task ${task.id}; ${error}`);
  })

  // setup subscriptions for topics
  for (const topic of topics) {
    const module = await import(`#topics/${topic}.js`);
    client.subscribe(topic, module.default());
  }
}

topics/updateTier_RetrieveMemberDetails.js:

'use strict';

// common modules
import { Variables } from 'camunda-external-task-client-js';

// custom modules
import patreonApi from '#utilities/patreonApi.js';


// topic handler
export default () => {
    return async ({ task, taskService }) => {
        try {
            console.log("TRIGGERED");
            await taskService.complete(task);
        } catch (error) {
            console.error(`Error retrieving member details: ${error.message}`);
            // report failure to platform
            try {
                await taskService.handleFailure(task, {
                    errorMessage: 'Unable to retrieve member details',
                    errorDetails: error.message,
                    retries: 0
                });
            } catch (taskServiceError) {
                // log to console as a last resort
                console.error(`Error reporting failure to Camunda: ${taskServiceError.message}`);
            }
        }
    };
};

As added info in case it might help, we are using Camunda with Keycloak for authorization. I’m mentioning this in case there’s some kind of auth issue preventing our process worker from seeing that the task is available, though it’s entirely a shot in the dark. This is how the OnStart scripts (which are run at the start of the processes look, both of which execute fine:

playSession_OnStart.js:

// initialize java classes and camunda services
const classAuthorization =
  Java.type('org.camunda.bpm.engine.authorization.Authorization');
const classResource =
  Java.type('org.camunda.bpm.engine.authorization.Resources');
const classPermissions =
  Java.type('org.camunda.bpm.engine.authorization.Permissions');
const serviceAuthorization = execution
  .getProcessEngineServices()
  .getAuthorizationService();

// retrieve process variables
const game = execution.getVariable('game');
const initiator = execution.getVariable('initiator');

// set business key based on process variables
execution.setProcessBusinessKey(
  '[' + game + '] ' + initiator
);

// add process instance authorization for initiator
const authorization = serviceAuthorization
  .createNewAuthorization(classAuthorization.AUTH_TYPE_GRANT);
authorization.setResource(classResource.PROCESS_INSTANCE);
authorization.setResourceId(execution.getProcessInstanceId());
authorization.setPermissions([classPermissions.READ]);
authorization.setUserId(initiator);
serviceAuthorization.saveAuthorization(authorization);

updateTier_OnStart.js:

// initialize java classes and camunda services
const classAuthorization =
  Java.type('org.camunda.bpm.engine.authorization.Authorization');
const classResource =
  Java.type('org.camunda.bpm.engine.authorization.Resources');
const classPermissions =
  Java.type('org.camunda.bpm.engine.authorization.Permissions');
const serviceAuthorization = execution
  .getProcessEngineServices()
  .getAuthorizationService();

// retrieve process variables
const memberId = execution.getVariable('memberId');
const initiator = execution.getVariable('initiator');

// stop execution if memberId is missing
if (!memberId) {
  throw new Error('MISSING_MEMBER_ID', 'The memberId variable is required.');
}

// TODO: determine this based on whether or not a body is provided in the request
//       (indicating that this is coming from a webhook)
const updateUser = true;

// initialize process variables
execution.setVariable('status', 'Initializing');
execution.setVariable('updateUser', updateUser);

// set business key based on process variables
execution.setProcessBusinessKey(
  '[updateTier] ' + memberId
);

// add process instance authorization for memberId
const authorization = serviceAuthorization
  .createNewAuthorization(classAuthorization.AUTH_TYPE_GRANT);
authorization.setResource(classResource.PROCESS_INSTANCE);
authorization.setResourceId(execution.getProcessInstanceId());
authorization.setPermissions([classPermissions.READ]);
authorization.setUserId(initiator);
serviceAuthorization.saveAuthorization(authorization);

Nevermind, that was not exactly the issue but it got me thinking along the right lines! My worker user did not have access to all resources, only the playSession resource. After setting it to an asterisk, it is now working as expected. Many thanks

1 Like