Prerequisites
fastify-cli@8.0.1 (published 2026-09-07) cannot start any server. fastify start throws before it reads the plugin file:
TypeError: pkgUp is not a function
at getPackageType (/usr/lib/node_modules/fastify-cli/util.js:74:32)
at requireServerPluginFromPath (/usr/lib/node_modules/fastify-cli/util.js:93:29)
at runFastify (/usr/lib/node_modules/fastify-cli/start.js:118:34)
I realise #914 and #913 are already open and both carry the fix. Filing this anyway because there is no issue tracking the released breakage, and the impact is wide enough that the patch release may be worth prioritising over the rest of those PRs.
Steps to reproduce
// plugin.mjs
export default async function plugin (app, opts) {
app.get('/', () => ({ ok: true }))
}
npx fastify-cli@8.0.1 start -a 127.0.0.1 -p 3999 plugin.mjs
curl http://127.0.0.1:3999/
Expected: {"ok":true}. Actual: the process exits with the TypeError above and nothing listens on the port.
Run against main and against fix/restore-test-suite in the same tree, replacing only util.js:
main: HTTP 000 TypeError: pkgUp is not a function
fix/restore-test-suite: HTTP 200 {"ok":true}
Cause
pkg-up was bumped ^3.1.0 -> ^5.0.0 in #679. That is not just a CJS/ESM change, the export shape changed too:
// pkg-up@3.1.0
module.exports = async ({cwd} = {}) => findUp('package.json', {cwd})
// pkg-up@5.0.0
export async function pkgUp({cwd} = {}) { return findUp('package.json', {cwd}) }
export function pkgUpSync({cwd} = {}) { return findUpSync('package.json', {cwd}) }
util.js still does const pkgUp = require('pkg-up'), so under require(esm) it binds the namespace { pkgUp, pkgUpSync } and the call fails. There is no default export to fall back to, require('pkg-up').default is undefined. const { pkgUp } = require('pkg-up'), which both open PRs use, is the right shape.
Why CI did not catch it
suite-runner.js on main uses glob's callback form, removed in glob v9:
glob(pattern, (err, matches) => { /* run() lives in here */ })
With glob@13 the call returns a promise and the callback never fires, so run() is never reached, no test file executes, and the script still exits 0:
const { glob } = require('glob')
let called = false
glob('**/*.json', () => { called = true })
setTimeout(() => console.log('callback fired:', called), 500)
// returned: Promise
// callback fired: false
So every suite has been reporting green on an empty set since glob went 11 -> 13 (#852, Dec 2025), which is how a fastify start-breaking change reached a release. The matches.length === 0 guard in #914 is the part that stops this recurring, and it seems worth having even if the rest of that PR needs more rounds.
Ask
A patch release with just the util.js one-liner would unblock everyone using fastify start from a global install, independently of how long #913/#914 take to land in full.
Environment
- fastify-cli: 8.0.1
- node: v26.7.0 and v26.8.1, same result
- os: Linux (Ubuntu 24.04, container) and the same on the host
Disclosure, since it affects how much you should trust the above: I am a human and I am filing this myself, but the investigation and the wording were done with Claude Code and I have not checked every line of it by hand. Everything above ships with the command that produced it, so please read the numbers as reproducible claims rather than as my personal verification. I am deliberately not opening a pull request, #914 already has the same fix and a duplicate from me would only add noise.
Prerequisites
fastify-cli@8.0.1(published 2026-09-07) cannot start any server.fastify startthrows before it reads the plugin file:I realise #914 and #913 are already open and both carry the fix. Filing this anyway because there is no issue tracking the released breakage, and the impact is wide enough that the patch release may be worth prioritising over the rest of those PRs.
Steps to reproduce
Expected:
{"ok":true}. Actual: the process exits with theTypeErrorabove and nothing listens on the port.Run against
mainand againstfix/restore-test-suitein the same tree, replacing onlyutil.js:Cause
pkg-upwas bumped^3.1.0->^5.0.0in #679. That is not just a CJS/ESM change, the export shape changed too:util.jsstill doesconst pkgUp = require('pkg-up'), so underrequire(esm)it binds the namespace{ pkgUp, pkgUpSync }and the call fails. There is nodefaultexport to fall back to,require('pkg-up').defaultisundefined.const { pkgUp } = require('pkg-up'), which both open PRs use, is the right shape.Why CI did not catch it
suite-runner.jsonmainuses glob's callback form, removed in glob v9:With
glob@13the call returns a promise and the callback never fires, sorun()is never reached, no test file executes, and the script still exits 0:So every suite has been reporting green on an empty set since
globwent 11 -> 13 (#852, Dec 2025), which is how afastify start-breaking change reached a release. Thematches.length === 0guard in #914 is the part that stops this recurring, and it seems worth having even if the rest of that PR needs more rounds.Ask
A patch release with just the
util.jsone-liner would unblock everyone usingfastify startfrom a global install, independently of how long #913/#914 take to land in full.Environment
Disclosure, since it affects how much you should trust the above: I am a human and I am filing this myself, but the investigation and the wording were done with Claude Code and I have not checked every line of it by hand. Everything above ships with the command that produced it, so please read the numbers as reproducible claims rather than as my personal verification. I am deliberately not opening a pull request, #914 already has the same fix and a duplicate from me would only add noise.