@noctcore/eslint-plugin-async-safety
Async correctness TypeScript cannot catch: unbounded fetch, dropped AbortSignals, concurrency races.
What it solves
Section titled “What it solves”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.
What it will yell at you about
Section titled “What it will yell at you about”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 and configure
Section titled “Install and configure”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.jsimport 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.
| Rule | What it reports | Preset | Fix | Types |
|---|---|---|---|---|
forward-abort-signal | A 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-mutation | A read-modify-write of an outer-scope binding inside a concurrent Promise.all(arr.map(async …)) callback can lose updates. | error | ||
no-leaky-race-timeout | A 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-state | A 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-awaits | Consecutive independent const x = await read() statements can run concurrently via await Promise.all([...]). | off | suggestion | |
require-client-timeout | A configured network client must be constructed with a timeout option; an unbounded client can hang forever. | error | ||
require-fetch-timeout | A fetch (or configured wrapper) call must carry a signal/timeout in its options — an unbounded request can hang forever. | error | suggestion |
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).