Finally got `pass otp` bash completions

I’ve been using pass to manage passwords, and the pass-otp extension to handle one-time-password tokens.

{ config, pkgs, ... }:

{
  environment.systemPackages = [
    (pkgs.pass.withExtensions (exts: [exts.pass-otp]))
  ];
  # ...
}

Problem

Bash completion for pass worked out of the box,

pass show foo<Tab> #=> "pass show foobar"

but not so for pass otp,

pass ot<Tab> # no completion
pass otp show foo<Tab> # no completion

I was perplexed by this, because both packages provide completion scripts.

And both completion scripts were installed:

for dir in ${XDG_DATA_DIRS//:/ }
do
    ls "$dir"/bash-completion/completions/pass* 2>/dev/null
done
/run/current-system/sw/share/bash-completion/completions/pass
/run/current-system/sw/share/bash-completion/completions/pass-otp

Solution

Poking around in /etc/bashrc, I noticed this comment:

Bash loads completions in all $XDG_DATA_DIRS/bash-completion/completions/ on demand…

The pass completion function is written in a way that allows extensions to register their additional completion functions, and the pass-otp completion script will indeed register it’s completion function when loaded. We just need to ensure that script actually gets loaded. We can’t rely on bash to load it on demand, since that strategy discovers the main command being completed (pass), not the subcommands provided by an extension.

To get it working, I added the following to my nixos config:

programs.bash.interactiveShellInit = ''
  . ${pkgs.pass.extensions.pass-otp}/share/bash-completion/completions/pass-otp
'';

By loading the pass-otp completion script upfront, when we eventually trigger completion of a pass command, the extension completion will already be registered, and thus visible to the pass completion function as well.