Summary
plugin-files / RegisterCommand is documented as the way to add nix subcommands (man nix.conf, manual). On current CppNix (reproduced on 2.36pre20260912_203f85b2), a plugin that registers a top-level command is visible in nix --help and NIX_GET_COMPLETIONS, but nix <cmd> … fails with '<cmd>' is not a recognised command.
This is the same class of bug as #2597 / the intent of #4486, still present on master.
Reproduction
- Build a trivial
RegisterCommand("scout") plugin (or any name) against the running nix / nix-cmd ABI.
- Load it without any PATH wrapper:
NIX_CONFIG="plugin-files = /path/to/nix-scout.so" nix --help | grep scout
# → · nix scout - …
NIX_GET_COMPLETIONS=1 NIX_CONFIG="plugin-files = …" nix '' | grep scout
# → scout is offered
NIX_CONFIG="plugin-files = …" nix scout --help
# → error: 'scout' is not a recognised command
Cause
In RootArgs::parseCmdline (src/libutil/args.cc), the first non-flag argument is handled via MultiCommand::processArgs, which looks up the subcommand in the pre-plugin command map and throws if missing.
initialFlagsProcessed() → initPlugins() → pluginsInited() (which refreshes commands = RegisterCommand::getCommandsFor({}) in src/nix/main.cc) runs only after the entire cmdline has been parsed.
That contradicts the comment on Args::initialFlagsProcessed() in args.hh (“Called after all command line flags before the first non-flag argument”) and the design from #4486 (load plugins after initial flags, then resolve the subcommand).
Expected
With plugin-files set in nix.conf (or as an initial flag before the subcommand), nix <plugin-cmd> … should dispatch to the plugin-registered command, matching the documented RegisterCommand API.
Related
Summary
plugin-files/RegisterCommandis documented as the way to addnixsubcommands (man nix.conf, manual). On current CppNix (reproduced on2.36pre20260912_203f85b2), a plugin that registers a top-level command is visible innix --helpandNIX_GET_COMPLETIONS, butnix <cmd> …fails with'<cmd>' is not a recognised command.This is the same class of bug as #2597 / the intent of #4486, still present on master.
Reproduction
RegisterCommand("scout")plugin (or any name) against the runningnix/nix-cmdABI.Cause
In
RootArgs::parseCmdline(src/libutil/args.cc), the first non-flag argument is handled viaMultiCommand::processArgs, which looks up the subcommand in the pre-plugin command map and throws if missing.initialFlagsProcessed()→initPlugins()→pluginsInited()(which refreshescommands = RegisterCommand::getCommandsFor({})insrc/nix/main.cc) runs only after the entire cmdline has been parsed.That contradicts the comment on
Args::initialFlagsProcessed()inargs.hh(“Called after all command line flags before the first non-flag argument”) and the design from #4486 (load plugins after initial flags, then resolve the subcommand).Expected
With
plugin-filesset innix.conf(or as an initial flag before the subcommand),nix <plugin-cmd> …should dispatch to the plugin-registered command, matching the documentedRegisterCommandAPI.Related
nixwrapper that intercepts the subcommand before invoking the real binary