deadhead

Install

Node 24 or newer. No configuration is required to start — the defaults are the point.

CLI

npx deadhead dist

It takes files, directories or globs, and expands globs itself so they behave the same in every shell:

deadhead dist                     # walk a directory for .html and .htm
deadhead "src/**/*.html"          # quote it; the CLI does the expanding
deadhead --format=sarif dist      # stylish (default), json, sarif
deadhead --fail-on=harmful dist   # exit 1 only on harmful findings
deadhead --fix dist               # rewrite files, then report what is left

Exit codes are the CI contract: 0 nothing at or above the --fail-on threshold, 1 threshold met, 2 usage, config or I/O error. A broken invocation never looks like a clean run.

Fixes are text edits, never re-serialised markup

--fix splices byte ranges out of the original file. It never parses your document and prints it back, because that rewrites quote style, attribute order, whitespace and character references across the whole file — turning a one-line fix into a thousand-line diff.

Two things are never fixed automatically: a rule declaring fix: { op: "none" }, and any rule whose detectability is partial. If the rule is not certain, it does not get to edit your file.

Suppressing a finding

<!-- deadhead-disable-next-line meta/http-equiv-x-ua-compatible -->
<!-- deadhead-disable link/shortcut-icon -->
<!-- deadhead-enable -->

A bare deadhead-disable with no rule id suppresses everything until the matching enable. Contents of <pre>, <code>, <textarea>, <samp> and <kbd> are skipped as content rather than markup.

Bookmarklet

A single IIFE with the rules inlined and no fetch, so a strict CSP cannot block it. It reads rendered output, which is the only ground truth when your head tags are assembled by a framework. Build it from the repository with pnpm build:bookmarklet.

ESLint

eslint-plugin-deadhead reports the same findings through @html-eslint/parser, with autofix. It is a convenience rather than the foundation: if your head tags live in JSX, Vue or Svelte, an HTML parser will not see them, and the bookmarklet will.

Full flag reference, configuration file and baseline documentation live in the README.