Skip to content

A function read from an object property silently drops its own call to a second function passed to it as a parameter (also read from an object property) — commander error-message output is lost #10711

Description

@proggeramlug

Found while validating the removal of the native commander binding (#10686) — with the binding
gone, import { Command } from "commander" now compiles the real npm package from source (via
#10699). Every value checked matches Node 26.5.1 exactly (program.args, boolean option defaults,
subcommand .action() callbacks, the commander.missingArgument/commander.unknownOption error
codes thrown) — except the error-message text commander writes to stderr before throwing, which
Perry silently drops.

Reproduction (isolated, no commander involved)

const config: any = {
  writeErr: (str: string) => process.stderr.write(str),
  outputError: (str: string, write: (s: string) => void) => write(str),
};

function fireError(cfg: any, message: string) {
  cfg.outputError(message, cfg.writeErr);
}

fireError(config, "error: something went wrong\n");
node main.ts          # prints "error: something went wrong"
perry compile main.ts -o out && ./out   # prints nothing

This is exactly the shape commander's own _displayError uses
(node_modules/commander/lib/command.js): a default outputConfiguration object holds two function
properties, writeErr: (str) => process.stderr.write(str) and
outputError: (str, write) => write(str), and error-reporting calls
this._outputConfiguration.outputError(message, this._outputConfiguration.writeErr) — an
object-property function invoking a SECOND object-property function passed to it as a parameter.

Isolating further

  • process.stderr.write alone works fine (confirmed separately).
  • A function stored as an object property, called directly (cfg.writeErr(message)), works fine.
  • The failure is specific to the two-level indirection: outputError (itself read off an
    object) invoking its own parameter write (itself a closure read off the same object and passed
    through one level of plain-function call). The inner write(str) call inside outputError's body
    never executes — no output, no crash, no error.

Impact

Any code that stores a callback on an object and calls it indirectly through a second function
(the "strategy"/"configurable output" pattern commander, and likely other npm packages, use) will
silently drop the inner call. For commander specifically: program.error(...),
exitOverride-driven validation failures (missing required argument, unknown option), and
--help/version output that routes through the same configuration object all lose their printed
text — though the actual thrown CommanderError (name, code, exitCode) is unaffected, since
that's a separate code path.

Not fixed by, and not caused by, #10686's binding removal — commander's own error codes,
program.args, option defaults, and .action() dispatch all match Node exactly in that PR's
acceptance test.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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