Skip to content

canonicalize calls plugin matchComponents callbacks with undefined values, crashing plugins that use the value (e.g. Phoenix heroicons) #20051

Description

@lessless

Disclosure: This issue (text, minimal repro, and analysis) was generated by AI — Claude Opus 4.7 — and reviewed by me before posting.

What version of Tailwind CSS are you using?
v4.3.0

What build tool (or framework if it abstracts the build tool) are you using?
CLI only (tailwindcss canonicalize)

What version of Node.js are you using?
v25.8.1

What operating system are you using?
macOS 14 / Darwin 24.6.0 (arm64)

Describe your issue

tailwindcss canonicalize crashes with TypeError: The "path" argument must be of type string or an instance of Buffer or URL. Received undefined whenever the loaded CSS registers a plugin that uses matchComponents with a values map, and the input class list contains an arbitrary-value utility (e.g. border-[1.5px], text-[26px]).

The crash comes from the user plugin's matcher callback being invoked with a value that is not in the values map, so destructured properties are undefined and any fs.readFileSync(fullPath) in the callback throws.

This affects the default Phoenix Heroicons plugin shipped by phx.new (https://github.com/phoenixframework/phoenix/blob/main/installer/templates/phx_assets/heroicons.js), so canonicalize is unusable for the entire Phoenix LiveView ecosystem out of the box.

Minimal reproduction

plugin-min.js:

const plugin = require("tailwindcss/plugin")
const fs = require("fs")
module.exports = plugin(function({matchComponents}) {
  matchComponents({
    "myicon": ({fullPath}) => {
      let content = fs.readFileSync(fullPath).toString()
      return { content: `'${content}'` }
    }
  }, { values: { foo: { fullPath: "/etc/hosts" } } })
})

app.css:

@import "tailwindcss" source(none);
@plugin "./plugin-min.js";
$ npx @tailwindcss/cli canonicalize --css ./app.css "border-[1.5px] flex"
The "path" argument must be of type string or an instance of Buffer or URL. Received undefined

Triggers and non-triggers

With this CSS loaded:

Input Result
flex OK
flex p-4 OK
myicon-foo OK (real value, matcher gets {fullPath: "/etc/hosts"})
border-[1.5px] flex crash
text-[26px] md:text-4xl crash
text-base leading-[1.1] crash
border-[1.5px] alone OK
text-[26px] alone OK

So the trigger is "arbitrary utility + at least one other utility", which suggests canonicalize is exploring shorthand collapse possibilities and invoking the matcher with a candidate that doesn't exist in values.

Expected behaviour

canonicalize should not invoke user matchComponents callbacks with values that are absent from the values map. Alternatively, if it must probe matchers speculatively, it should swallow exceptions from those calls — they're being used for analysis, not output generation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions