Summary
Every CLI error in aio is printed as a raw Node.js uncaught-exception dump (source frame, ^ caret, full stack, Node.js vXX footer) instead of oclif's clean › Error: <message> formatting. This affects all error paths — arg/flag parse errors, this.error() validation, and command runtime errors alike.
Root cause: bin/run passes the @oclif/core/handle module object to Promise.prototype.catch() instead of its handle function. Since @oclif/core v4 exports handle as a named export ({ handle, Exit }), .catch(<object>) is a no-op per the Promise spec, the rejection goes unhandled, and Node prints the raw stack and exits.
Steps to reproduce
$ aio runtime ip-list get --region MARS
/Users/.../@oclif/core/lib/errors/error.js:44
err = new cli_1.CLIError(input, options);
^
CLIError: invalid region "MARS". Expected one of: amer, emea, apac, aus
at Object.error (.../@oclif/core/lib/errors/error.js:44:15)
...
at async AIOCommand.run (.../@adobe/aio-cli/src/index.js:38:12)
Node.js v24.14.1
Reproduces on any command that errors, e.g.:
aio runtime:action:invoke → RequiredArgsError: Missing 1 required arg (raw stack)
aio runtime property get --bogusflag → NonExistentFlagsError: Nonexistent flag (raw stack)
Expected: a clean, single-line message, e.g. › Error: invalid region "MARS". Expected one of: amer, emea, apac, aus.
Root cause
bin/run:
require('../src/').run()
.then(() => require('@oclif/core/flush'))
.catch(require('@oclif/core/handle'))
In @oclif/core v4, the ./handle and ./flush subpath exports are modules with named exports, not callable functions:
> require('@oclif/core/handle')
{ Exit: { exit: [Function] }, handle: [Function: handle] } // typeof === 'object', not callable
> require('@oclif/core/flush')
{ flush: [Function: flush] } // typeof === 'object', not callable
So:
.catch(require('@oclif/core/handle')) hands .catch() a non-function. Per the Promise spec a non-function onRejected is ignored, the rejection propagates as an unhandled promise rejection, and Node (which treats unhandled rejections as fatal since v15, default-throw hardened in later releases) prints the raw stack and exits. oclif's pretty-printer (pretty-print → › Error:) is never reached.
.then(() => require('@oclif/core/flush')) only requires the flush module and returns it — flush() is never called, so buffered stdout may not be flushed before exit.
This regressed when @adobe/aio-cli moved to @oclif/core v4 (current dep: @oclif/core: ^4.9.0). In @oclif/errors / older @oclif/core, handle/flush were exported as callable defaults, so the old bin/run worked. With v4's named exports the wiring silently breaks.
Proof
// reproduces the raw stack — object passed to .catch() is ignored, rejection is unhandled
Promise.reject(new Error('boom')).catch(require('@oclif/core/handle'))
// clean — the actual function handles it (prints " Error: boom" and exits)
Promise.reject(Object.assign(new Error('boom'), { oclif: { exit: 1 } }))
.catch(require('@oclif/core/handle').handle)
Suggested fix
Pass the functions, not the module objects:
require('../src/').run()
.then(() => require('@oclif/core/flush').flush())
.catch((error) => require('@oclif/core/handle').handle(error))
(or destructure: const { handle } = require('@oclif/core/handle') / const { flush } = require('@oclif/core/flush').)
Impact
Environment
@adobe/aio-cli 11.1.0 (also present on master)
@oclif/core 4.11.4 (dep ^4.9.0)
- Node.js v24.14.1 (mechanism applies to Node ≥ 15; consistently fatal on Node 22+)
- macOS (Darwin arm64)
Summary
Every CLI error in
aiois printed as a raw Node.js uncaught-exception dump (source frame,^caret, full stack,Node.js vXXfooter) instead of oclif's clean› Error: <message>formatting. This affects all error paths — arg/flag parse errors,this.error()validation, and command runtime errors alike.Root cause:
bin/runpasses the@oclif/core/handlemodule object toPromise.prototype.catch()instead of itshandlefunction. Since@oclif/corev4 exportshandleas a named export ({ handle, Exit }),.catch(<object>)is a no-op per the Promise spec, the rejection goes unhandled, and Node prints the raw stack and exits.Steps to reproduce
Reproduces on any command that errors, e.g.:
aio runtime:action:invoke→RequiredArgsError: Missing 1 required arg(raw stack)aio runtime property get --bogusflag→NonExistentFlagsError: Nonexistent flag(raw stack)Expected: a clean, single-line message, e.g.
› Error: invalid region "MARS". Expected one of: amer, emea, apac, aus.Root cause
bin/run:In
@oclif/corev4, the./handleand./flushsubpath exports are modules with named exports, not callable functions:So:
.catch(require('@oclif/core/handle'))hands.catch()a non-function. Per the Promise spec a non-functiononRejectedis ignored, the rejection propagates as an unhandled promise rejection, and Node (which treats unhandled rejections as fatal since v15, default-throwhardened in later releases) prints the raw stack and exits. oclif's pretty-printer (pretty-print→› Error:) is never reached..then(() => require('@oclif/core/flush'))onlyrequires the flush module and returns it —flush()is never called, so buffered stdout may not be flushed before exit.This regressed when
@adobe/aio-climoved to@oclif/corev4 (current dep:@oclif/core: ^4.9.0). In@oclif/errors/ older@oclif/core,handle/flushwere exported as callable defaults, so the oldbin/runworked. With v4's named exports the wiring silently breaks.Proof
Suggested fix
Pass the functions, not the module objects:
(or destructure:
const { handle } = require('@oclif/core/handle')/const { flush } = require('@oclif/core/flush').)Impact
aio runtime ip-list get(aio runtime ip-list get --regionis case-sensitive and errors on uppercase region values aio-cli-plugin-runtime#425 / ACNA-4652) in a full stack trace, defeating its purpose.flush()never running risks truncated/missing output on exit in some terminals/pipes.Environment
@adobe/aio-cli11.1.0 (also present onmaster)@oclif/core4.11.4 (dep^4.9.0)