no-floating-workers
Require worker handles to be retained so they can be terminated during cleanup.
Rule catalog ID: R004
Targeted pattern scopeโ
This rule targets worker constructors whose active thread or shared worker connection should have an explicit lifecycle owner:
WorkerSharedWorkerwindow.Worker,self.Worker, andglobalThis.Workerwindow.SharedWorker,self.SharedWorker, andglobalThis.SharedWorkerWorkerimported fromnode:worker_threadsorworker_threads
The rule reports worker instances that are immediately discarded or chained
directly into a method call other than .terminate(). In both cases there is no
remaining worker handle available for teardown.
What this rule reportsโ
The rule reports:
- standalone worker construction expressions such as
new Worker("./worker.js"); - voided worker construction such as
void new SharedWorker("./worker.js"); - immediate worker method chains such as
new Worker("./worker.js").postMessage("start"); - discarded Node.js
worker_threadsworkers imported asWorker
It intentionally does not require same-function terminate() calls. Ownership
can be transferred to a component instance, resource manager, returned
disposable, using declaration, or longer-lived runtime owner.
Why this rule existsโ
Workers run independently from the creating script. Browser Worker instances
can be stopped with terminate(), and Node.js worker_threads.Worker
instances expose terminate() and Symbol.asyncDispose. If the worker handle is
not retained, later cleanup cannot reliably stop the work or release the owning
runtime resource.
SharedWorker instances are intentionally shared across same-origin browsing
contexts, but the creating page still needs a retained handle for its port and
ownership bookkeeping. Discarding the handle makes that lifecycle implicit.
Incorrectโ
new Worker("./worker.js");
void new SharedWorker("./shared-worker.js");
new Worker("./worker.js").postMessage("start");
import { Worker } from "node:worker_threads";
new Worker("./worker.js");
Correctโ
const worker = new Worker("./worker.js");
worker.postMessage("start");
worker.terminate();
return new Worker("./worker.js");
registerWorker(new SharedWorker("./shared-worker.js"));
import { Worker } from "node:worker_threads";
await using worker = new Worker("./worker.js");
Behavior and migration notesโ
Store worker instances in the lifecycle owner that will stop or dispose them. For UI components, that is usually the component setup scope or a disposable collection. For shared libraries, returning the worker or passing it to a dedicated resource manager keeps ownership explicit.
This rule does not autofix. Choosing the owner and termination point is a
semantic decision, and inserting a variable without a matching terminate() or
disposal path would hide the lifecycle problem instead of fixing it.
ESLint flat config exampleโ
import runtimeCleanup from "eslint-plugin-runtime-cleanup";
export default [
runtimeCleanup.configs.recommended,
];
When not to use itโ
Do not enable this rule for code that intentionally creates page-lifetime or process-lifetime workers and does not need an explicit cleanup owner. Prefer a narrow disable comment with a reason when a worker is meant to live for the whole runtime.