denoland/deno · error · Error

Destroy hook of "${id}" errored

Error message

Destroy hook of "${id}" errored

What it means

Thrown when a rule's optional `destroy()` hook raises. During lint setup (cli/js/40_lint.js, runPluginsForFile) destroy hooks are collected and each is wrapped so a failure is reported as 'Destroy hook of "<plugin>/<rule>" errored' with the original error as `cause`. Destroy runs after linting a file finishes, for cleanup.

Source

Thrown at cli/js/40_lint.js:1305

              });
            }
          };

          if (isExit) {
            info.exit = wrapped;
          } else {
            info.enter = wrapped;
          }
        }
      }

      if (typeof rule.destroy === "function") {
        const destroyFn = rule.destroy.bind(rule);
        destroyFns.push(() => {
          try {
            destroyFn(ruleCtx);
          } catch (err) {
            throw new Error(`Destroy hook of "${id}" errored`, { cause: err });
          }
        });
      }
    }
  }

  // Create selectors
  /** @type {TransformFn} */
  const toElem = (str) => {
    const id = ctx.typeByStr.get(str);
    return id === undefined ? 0 : id;
  };
  /** @type {TransformFn} */
  const toAttr = (str) => {
    const id = ctx.propByStr.get(str);
    return id === undefined ? 0 : id;
  };

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Look at err.cause for the real stack inside your destroy() implementation
  2. Make destroy defensive: only clean up what was actually initialized, and tolerate partially-populated state
  3. If the hook has nothing to clean up, remove it entirely

Example fix

// before
destroy(ctx) {
  this.seen.forEach((n) => n.parent.unregister(this));
},

// after
destroy(ctx) {
  for (const entry of this.acquired) entry.release();
},
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await runLint(fixture);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("Destroy hook of")) {
    // inspect err.cause for the real cleanup failure
    console.error("cleanup bug:", err.cause ?? err);
  }
  throw err;
}

Prevention

When it happens

Trigger: A rule defines `destroy(ctx)` and the function throws: releasing a resource that was never acquired, referencing state that a crashed visitor left inconsistent, or calling a method on undefined cleanup state.

Common situations: Rules that accumulate per-file state (maps of imports, reported spans) and clean up in destroy; a destroy hook that assumes create() ran successfully for every file; refactoring a rule and forgetting destroy still reads an old field.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/13fec3ea501db958. Report an issue: GitHub.