Repository: vercel/next.js
File: packages/next/src/server/lib/start-server.ts (TypeScript)

Code before:
              try {
                const { traceGlobals } =
                  require('../../trace/shared') as typeof import('../../trace/shared')
                const telemetry = traceGlobals.get('telemetry') as
                  | InstanceType<
                      typeof import('../../telemetry/storage').Telemetry
                    >
                  | undefined
                if (telemetry) {
                  // Use flushDetached to avoid blocking process exit
                  // Each process writes to a unique file (_events_${pid}.json)
                  // to avoid race conditions with the parent process
                  telemetry.flushDetached('dev', dir)
                }
              } catch (_) {
                // Ignore telemetry errors during cleanup
              }
            }

            debug('start-server process cleanup finished')

            // Close the Node.js inspector if it's open. The inspector keeps
            // the event loop alive which prevents the process from exiting.
            if (inspector.url()) {
              inspector.close()
            }

            process.exit(0)
          })()
        }

        // Make sure commands gracefully respect termination signals (e.g. from Docker)
        // Allow the graceful termination to be manually configurable
        if (!process.env.NEXT_MANUAL_SIG_HANDLE) {
          process.on('SIGINT', cleanup)
          process.on('SIGTERM', cleanup)
        }

        // Now load config via getRequestHandlers (single loadConfig call)
        const initResult = await getRequestHandlers({
          dir,
          port,
          isDev,
          onDevServerCleanup: cleanupListeners
            ? cleanupListeners.add.bind(cleanupListeners)
            : undefined,
          server,
          hostname,
          minimalMode,
          keepAliveTimeout,
          experimentalHttpsServer: !!selfSignedCertificate,

Review comment (bug):
The contract with Node.js applications is that you close the debugger if you opened it. That's how Node.js behaves by default so we should have a strong reason why Next.js should behave differently.

I suspect you wouldn't be able to take a heap snapshot anymore after we closed the connection? Also important to check how this interacts with a running profiling session.

Code after:
                const { traceGlobals } =
                  require('../../trace/shared') as typeof import('../../trace/shared')
                const telemetry = traceGlobals.get('telemetry') as
                  | InstanceType<
                      typeof import('../../telemetry/storage').Telemetry
                    >
                  | undefined
                if (telemetry) {
                  // Use flushDetached to avoid blocking process exit
                  // Each process writes to a unique file (_events_${pid}.json)
                  // to avoid race conditions with the parent process
                  telemetry.flushDetached('dev', dir)
                }
              } catch (_) {
                // Ignore telemetry errors during cleanup
              }
            }

            debug('start-server process cleanup finished')

            // Exit with signal-based exit code (128 + signal number) so that
            // Node.js treats this as a signal termination, not a normal exit.
            // This avoids waiting for the debugger to disconnect.
            switch (signal) {
              case 'SIGINT':
                process.exit(130)
                break
              case 'SIGTERM':
                process.exit(143)
                break
              default:
                // Make sure all handled signals have explicit exit codes.
                // This is just a fallback to guard against unsound types.
                signal satisfies never
                process.exit(128)
            }
          })()
        }

        // Make sure commands gracefully respect termination signals (e.g. from Docker)
        // Allow the graceful termination to be manually configurable
        if (!process.env.NEXT_MANUAL_SIG_HANDLE) {
          process.on('SIGINT', cleanup)
          process.on('SIGTERM', cleanup)
        }

        // Now load config via getRequestHandlers (single loadConfig call)
        const initResult = await getRequestHandlers({
          dir,
          port,
          isDev,