Skip to content

CLI: async command failures surface as an unhandled promise rejection #1056

Description

@yashksaini-coder

What happens

The CLI entrypoint parses argv with the synchronous Command.parse():

// packages/cli/src/index.ts
if (detectIsMainModule()) {
	createProgram().parse(normalizeLegacyArgs(process.argv));
}

But every command action is wired as async in base.command.ts:

command.action(async (...data) => {
	...
	await this.action({ args, options: commandOptions });
});

.parse() does not await the promise returned by an async action, and there is no top-level handler. So when an action rejects — e.g. a failed OAuth token exchange during corsair setup, or a network error in watch-renew — Node prints a raw UnhandledPromiseRejection stack trace instead of the clean [#corsair]: ... diagnostic the rest of the CLI uses (12 call sites, e.g. utils/corsair-instance.ts).

Impact

Confusing UX on every failing command; the process can also exit 0 on older Node versions before the rejection is reported. Affects setup, auth, list, schema, watch-renew, studio, script, and the subscribe commands.

Expected

A rejecting action should print a single [#corsair]: <message> line and exit non-zero.

Fix

Use .parseAsync() with a top-level .catch(). Commander already handles its own parse/help/version exits synchronously, so this only affects genuine action errors. PR attached.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions