Skip to content

nest start hangs when child process exits - regression in 11.0.18 #3390

Description

@ghislain-sn

Did you read the migration guide?

  • I have read the whole migration guide

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Potential Commit/PR that introduced the regression

PR #3313

Versions

11.0.17 → 11.0.18

Describe the regression

Description, and workaround generated by an IA.
The workaround resolves our issue, but I think there is still a bug on your end.


nest start hangs indefinitely when the child process exits on its own (e.g. process.exit(0)).

I use process.exit(0) after microservice initialization to validate startup in a CI pipeline. The startup test works fine on sub-apps that have no watchAssets configured, but hangs on apps where root-level compilerOptions.assets with watchAssets: true are inherited — even though the asset globs don't match any files for that sub-app.

Works on 11.0.17, hangs on 11.0.18+.

Running with node dist/main.js always exits correctly. Only nest start hangs.

Root cause

In 11.0.18, closeWatchers() in assets-manager.js was changed from a setTimeout-based approach to Promise.all(watcherReadyPromises).then(close).

When an asset glob with watchAssets: true matches no files (common in monorepos where root-level assets don't exist in every sub-app's sourceRoot), glob.sync() returns [] and chokidar.watch([]) never emits 'ready'. This blocks Promise.all() from ever resolving, so all FSWatchers — including ones watching real files — are never closed. The parent process hangs.

Workaround

Move compilerOptions.assets from the root config to each project's own compilerOptions in nest-cli.json, so that only apps that actually have those files get the watchers. This avoids the empty glob scenario.

Suggested fix

Skip watcher creation when the glob matches nothing:

const matchedPaths = glob.sync(item.glob, { ignore: item.exclude, dot: true });
if (matchedPaths.length === 0) return; // chokidar.watch([]) never emits 'ready'
const watcher = chokidar.watch(matchedPaths)...

Minimum reproduction code

// nest-cli.json — one glob matches files, the other matches nothing
{
  "sourceRoot": "src",
  "compilerOptions": {
    "assets": [
      { "include": "i18n/**/*.json", "watchAssets": true, "outDir": "dist/src" },
      { "include": "templates/**/*", "watchAssets": true, "outDir": "dist/src" }
    ]
  }
}
// src/templates/page.html exists → creates real FSWatcher handles
// src/i18n/ does NOT exist → chokidar.watch([]) → 'ready' never fires → Promise.all blocks
// main.ts — app that exits after startup (CI startup validation)
const app = await NestFactory.create(AppModule);
await app.listen(0);
await app.close();
process.exit(0);
  • node dist/main.js → exits immediately
  • nest start → hangs forever

Expected behavior

nest start should exit when the child process exits, regardless of whether some asset globs match no files.

Other

Node.js: v22.22.0
OS: Linux
Works on 11.0.17, broken on 11.0.18

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions