Legacy Cron node (n8n-nodes-base.cron) not recognized as activatable trigger — isTriggerNode() only matches nodes with 'trigger' in the type name
Description
Calling n8n_update_partial_workflow on any active workflow that uses the legacy n8n-nodes-base.cron trigger node fails validation with:
Cannot activate workflow: No activatable trigger nodes found. Workflows must have at least one enabled trigger node (webhook, schedule, executeWorkflowTrigger, etc.).
This happens on any update to the workflow (not just activate/deactivate calls) — a plain updateNode/patchNodeField edit to an unrelated node is enough to trigger it, because validation re-checks the whole workflow's trigger eligibility on every save while active: true.
The workflow is, and remains, fully active and running correctly in n8n itself the entire time — this is purely a client-side validation false negative in n8n-mcp, not a real n8n limitation. Confirmed by calling n8n's own REST API directly (POST /workflows/{id}/activate and /deactivate) for the same workflow — both succeed with HTTP 200 every time.
This is the same class of bug as #538 (IMAP Email Trigger), just for a different, more common node type.
Root Cause (confirmed by reading source)
src/utils/node-type-utils.ts:
export function isTriggerNode(nodeType: string): boolean {
const normalized = normalizeNodeType(nodeType);
const lowerType = normalized.toLowerCase();
if (lowerType.includes('trigger')) {
return true;
}
if (lowerType.includes('webhook') && !lowerType.includes('respond')) {
return true;
}
if (lowerType.includes('emailread') || lowerType.includes('emailreadimap')) {
return true;
}
return normalized === 'nodes-base.start';
}
n8n-nodes-base.cron normalizes to nodes-base.cron, which does not contain the substring trigger, webhook, or emailread, and is not nodes-base.start — so it falls through to false.
Notably, the docstring immediately above isActivatableTrigger (which calls isTriggerNode) explicitly claims it handles this case:
/**
* Check if a node is an ACTIVATABLE trigger
* ...
* Returns true for:
* - Webhook triggers (webhook, webhookTrigger)
* - Time-based triggers (schedule, cron) <-- claims "cron" is handled
* ...
*/
...but the actual isTriggerNode implementation never checks for 'cron' as a substring anywhere. The docstring and the code have drifted apart.
Steps to Reproduce
- Have an active workflow using a legacy
n8n-nodes-base.crontrigger node (as opposed ton8n-nodes-base.scheduleTrigger) - Call
n8n_update_partial_workflowwith any operation targeting a different node in the same workflow, e.g.:n8n_update_partial_workflow({ id: "<workflow-id>", operations: [{ type: "patchNodeField", nodeName: "SomeOtherNode", fieldPath: "parameters.someField", patches: [{ find: "a", replace: "b" }] }] }) - Validation fails with "No activatable trigger nodes found", operation is not saved, even though the Cron node is enabled and the workflow is genuinely active and running in n8n
Suggested Fix
Add a check for 'cron' as a substring in isTriggerNode, matching what the docstring already claims and matching how emailread is already handled:
if (lowerType.includes('cron')) {
return true;
}
Environment
- n8n-mcp version: 2.77.0 (confirmed via
n8n_health_check, reported as up to date) - n8n version: 2.37.7
- Node type:
n8n-nodes-base.cron(typeVersion 1)
Workaround
Two working options, confirmed directly:
- Call n8n's REST API directly (
POST /workflows/{id}/activate//deactivate, orPUT /workflows/{id}), bypassing n8n-mcp's client-side validation entirely. - Replace the legacy Cron node with the modern
n8n-nodes-base.scheduleTriggernode — its type name containstrigger, so it passesisTriggerNode()and the whole class of error goes away permanently for that workflow.
Source: czlonkowski/n8n-mcp