feat(core): Recommend sleep helper for banned setTimeout/clearTimeout (#35114)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Guillaume F. 2026-07-29 14:16:38 +02:00 committed by GitHub
parent 5fbf1b2c94
commit fd84b602a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 62 additions and 7 deletions

View File

@ -33,12 +33,26 @@ export class MyNode implements INodeType {
### ✅ Correct
```typescript
import { sleep } from 'n8n-workflow';
export class MyNode implements INodeType {
async execute(this: IExecuteFunctions) {
// Use n8n context methods instead
const timezone = this.getTimezone();
// Use the sleep helper instead of setTimeout
await sleep(1000);
return this.prepareOutputData([]);
}
}
```
## Alternatives to restricted timer globals
`n8n-workflow` exports helpers that work the same everywhere, including on
n8n Cloud, instead of reaching for the restricted timer globals directly:
- Instead of `setTimeout(resolve, ms)`, use `await sleep(ms)`.
- Instead of `setTimeout` + `clearTimeout` for a cancellable delay, use
`await sleepWithAbort(ms, abortSignal)`.

View File

@ -70,6 +70,17 @@ var __dirname = '/path';
const __filename = 'file.js';
`,
},
{
name: 'sleep and sleepWithAbort from n8n-workflow should be allowed',
code: `
import { sleep, sleepWithAbort } from 'n8n-workflow';
async function wait(signal) {
await sleep(1000);
await sleepWithAbort(1000, signal);
}
`,
},
],
invalid: [
{
@ -82,7 +93,12 @@ const __filename = 'file.js';
},
{
code: 'setTimeout(() => {}, 1000);',
errors: [{ messageId: 'restrictedGlobal', data: { name: 'setTimeout' } }],
errors: [
{
messageId: 'restrictedGlobalWithHint',
data: { name: 'setTimeout', hint: "Use the 'sleep' helper from 'n8n-workflow' instead." },
},
],
},
{
code: 'clearInterval(timer);',
@ -90,7 +106,15 @@ const __filename = 'file.js';
},
{
code: 'clearTimeout(timer);',
errors: [{ messageId: 'restrictedGlobal', data: { name: 'clearTimeout' } }],
errors: [
{
messageId: 'restrictedGlobalWithHint',
data: {
name: 'clearTimeout',
hint: "Use 'sleepWithAbort' from 'n8n-workflow' with an AbortSignal instead.",
},
},
],
},
{
code: 'setInterval(() => {}, 1000);',

View File

@ -17,6 +17,12 @@ const restrictedGlobals = [
'__filename',
];
// Nudge toward the n8n-workflow alternatives that work under n8n Cloud's restrictions
const restrictedGlobalHints: Record<string, string> = {
setTimeout: "Use the 'sleep' helper from 'n8n-workflow' instead.",
clearTimeout: "Use 'sleepWithAbort' from 'n8n-workflow' with an AbortSignal instead.",
};
export const NoRestrictedGlobalsRule = createRule({
name: 'no-restricted-globals',
meta: {
@ -26,6 +32,7 @@ export const NoRestrictedGlobalsRule = createRule({
},
messages: {
restrictedGlobal: "Use of restricted global '{{ name }}' is not allowed",
restrictedGlobalWithHint: "Use of restricted global '{{ name }}' is not allowed. {{ hint }}",
},
schema: [],
},
@ -43,11 +50,21 @@ export const NoRestrictedGlobalsRule = createRule({
return;
}
context.report({
node: ref.identifier,
messageId: 'restrictedGlobal',
data: { name },
});
const hint = restrictedGlobalHints[name];
context.report(
hint
? {
node: ref.identifier,
messageId: 'restrictedGlobalWithHint',
data: { name, hint },
}
: {
node: ref.identifier,
messageId: 'restrictedGlobal',
data: { name },
},
);
}
return {