Automattic/harper · error · Error

WorkerLinter has been disposed.

Error message

WorkerLinter has been disposed.

What it means

WorkerLinter.rpc checks this.disposed before queueing any request to the worker. After calling linter.dispose(), every public method (lint, organizedLints, applySuggestion, isLikelyEnglish, isolateEnglish, loadWeirpackFromBytes, and setup itself) throws 'WorkerLinter has been disposed.' to prevent use of a terminated worker.

Source

Thrown at packages/harper.js/src/WorkerLinter/index.ts:244

		return await this.rpc('loadWeirpackFromBytes', [arr]);
	}

	/**
	 * Load a Weirpack from bytes via the worker thread.
	 *
	 * Returns the failure report if tests fail or `undefined` when the pack is imported.
	 */
	async loadWeirpackFromBytes(
		bytes: Uint8Array | number[],
	): Promise<WeirpackTestFailures | undefined> {
		const arr = Array.from(bytes);
		return await this.rpc('loadWeirpackFromBytes', [arr]);
	}

	/** Run a procedure on the remote worker. */
	private async rpc(procName: string, args: unknown[]): Promise<any> {
		if (this.disposed) {
			throw new Error('WorkerLinter has been disposed.');
		}

		const promise = new Promise((resolve, reject) => {
			this.requestQueue.push({
				resolve,
				reject,
				request: { procName, args },
			});

			this.submitRemainingRequests();
		});

		return promise;
	}

	private async submitRemainingRequests() {
		if (this.working) {
			return;

View on GitHub (pinned to 5fe7d5ab76)

Solutions

  1. Don't call methods after dispose(); recreate a new WorkerLinter instance if you need to lint again
  2. Guard calls with a disposed flag or store the instance in a ref that is nulled on cleanup
  3. Await in-flight lint promises before disposing, or catch this error in cleanup racing
  4. Reset the singleton on HMR/StrictMode remount instead of reusing the disposed one

Example fix

// before
useEffect(() => () => linter.dispose(), []); // disposed, then reused
// after
const ref = useRef<WorkerLinter>();
useEffect(() => {
  ref.current = new WorkerLinter({ binary: wasm });
  return () => { ref.current?.dispose(); ref.current = undefined; };
}, []);
// at call site: ref.current ??= new WorkerLinter(...);
Defensive patterns

Strategy: try-catch

Validate before calling

function ensureUsable(linter: WorkerLinter) {
  if (!linter) throw new Error('linter not initialized');
  return linter;
}

Try / catch

try {
  return await linter.lint(text);
} catch (e) {
  if (e instanceof Error && e.message === 'WorkerLinter has been disposed.') {
    linter = createLinter(); // recreate after dispose
    return await linter.lint(text);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling any WorkerLinter method after linter.dispose() — commonly in React effects whose cleanup disposes the linter while async lint calls are still in flight, or reusing a cached linter after page/navigation teardown.

Common situations: React 18 StrictMode double mount/unmount disposing the linter; disposing on unmount while a debounced lint is pending; framework hot reloads recreating components but reusing a disposed singleton.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of Automattic/harper@5fe7d5ab76 (2026-09-06). Data as JSON: /api/errors/320aac87dc1935b4. Report an issue: GitHub.