Skip to content

@noctcore/eslint-plugin-async-safety

Async correctness TypeScript cannot catch: unbounded fetch, dropped AbortSignals, concurrency races.

An async bug usually type-checks. A fetch with no timeout hangs a worker forever, a function that accepts an AbortSignal and never forwards it cannot be cancelled, two iterations of a Promise.all(arr.map(async ...)) read and write the same counter and lose an update, a module-level variable written in a request handler leaks between requests.

It will yell at you about timeouts and cancellation first: require-fetch-timeout fires on every bare fetch, which is intentional and noisy on first run. no-shared-mutable-module-state and require-client-timeout ship enabled but inert until you tell them which files are server code and which clients to check. Timeouts and parallelism change runtime behaviour, so the fixes are editor suggestions, never autofixes. prefer-parallel-awaits ships off: sequential awaits are often deliberate. It is a bad fit for browser-only code that relies on the platform’s own fetch lifetime, and for scripts where hanging is harmless.

Install
npm install --save-dev @noctcore/eslint-plugin-async-safety @typescript-eslint/parser
# or: bun add -D / pnpm add -D @noctcore/eslint-plugin-async-safety @typescript-eslint/parser
eslint.config.js
// eslint.config.js
import tsParser from '@typescript-eslint/parser';
import asyncSafety from '@noctcore/eslint-plugin-async-safety';
export default [
{
...asyncSafety.configs.recommended,
files: ['**/*.{ts,tsx}'],
languageOptions: { parser: tsParser },
},
];
// Rules outside the preset are enabled one by one, e.g.
// { rules: { 'noctcore-async-safety/prefer-parallel-awaits': 'error' } }

The recommended preset enables 6 of 7 rules.

Each rule links to its page, with the options it takes and Incorrect and Correct examples. A good first read is forward-abort-signal.

RuleWhat it reportsPresetFixTypes
forward-abort-signalA function that accepts an AbortSignal (param named signal or typed AbortSignal) but awaits a call without ever forwarding it leaves that work uncancellable.error
no-concurrent-shared-mutationA read-modify-write of an outer-scope binding inside a concurrent Promise.all(arr.map(async …)) callback can lose updates.error
no-leaky-race-timeoutA setTimeout timeout raced with Promise.race must be cleared — otherwise the timer outlives the race whenever the other promise wins.error
no-shared-mutable-module-stateA module-scoped mutable binding written inside an exported async/handler function is shared across concurrent requests. Opt in per file via include.error
prefer-parallel-awaitsConsecutive independent const x = await read() statements can run concurrently via await Promise.all([...]).offsuggestion
require-client-timeoutA configured network client must be constructed with a timeout option; an unbounded client can hang forever.error
require-fetch-timeoutA fetch (or configured wrapper) call must carry a signal/timeout in its options — an unbounded request can hang forever.errorsuggestion

Preset: severity in configs.recommended; off means the preset registers the rule switched off, not listed means it leaves the rule out; both are opt-in, so you turn the rule on yourself. Fix: whether the rule ships an autofix or an editor suggestion. Types: whether the rule needs a type-checked program (parserOptions.projectService).