#5133·chezmoi

PowerShell Completion missing Native parameter

Author: jfisheCreated Jul 17, 2026Updated Jul 20, 2026
Labelsbugin dependency

Describe the bug

chezmoi completion powershell produces:

Register-ArgumentCompleter -CommandName 'chezmoi' -ScriptBlock ${__chezmoiCompleterBlock} which does not include the Native parameter. Expected behavior is:

Register-ArgumentCompleter -Native -CommandName 'chezmoi' -ScriptBlock ${__chezmoiCompleterBlock}

To reproduce

powershell
pwsh --noprofile
chezmoi completion powershell

Expected behavior

Register-ArgumentCompleter -Native -CommandName 'chezmoi' -ScriptBlock ${__chezmoiCompleterBlock} -Native is missing from chezmoi generated PowerShell.

Output of command with the --verbose flag

bash
$ chezmoi --verbose completion powershell
powershell
# powershell completion for chezmoi                              -*- shell-script -*-

function __chezmoi_debug {
    if ($env:BASH_COMP_DEBUG_FILE) {
        "$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE"
    }
}

filter __chezmoi_escapeStringWithSpecialChars {
    $_ -replace '\s|#|@|\$|;|,|''|\{|\}|\(|\)|"|`|\||<|>|&','`$&'
}

[scriptblock]${__chezmoiCompleterBlock} = {
    param(
            $WordToComplete,
            $CommandAst,
            $CursorPosition
        )

    # Get the current command line and convert into a string
    $Command = $CommandAst.CommandElements
    $Command = "$Command"

    __chezmoi_debug ""
    __chezmoi_debug "========= starting completion logic =========="
    __chezmoi_debug "WordToComplete: $WordToComplete Command: $Command CursorPosition: $CursorPosition"

    # The user could have moved the cursor backwards on the command-line.
    # We need to trigger completion from the $CursorPosition location, so we need
    # to truncate the command-line ($Command) up to the $CursorPosition location.
    # Make sure the $Command is longer then the $CursorPosition before we truncate.
    # This happens because the $Command does not include the last space.
    if ($Command.Length -gt $CursorPosition) {
        $Command=$Command.Substring(0,$CursorPosition)
    }
    __chezmoi_debug "Truncated command: $Command"

    $ShellCompDirectiveError=1
    $ShellCompDirectiveNoSpace=2
    $ShellCompDirectiveNoFileComp=4
    $ShellCompDirectiveFilterFileExt=8
    $ShellCompDirectiveFilterDirs=16
    $ShellCompDirectiveKeepOrder=32

    # Prepare the command to request completions for the program.
    # Split the command at the first space to separate the program and arguments.
    $Program,$Arguments = $Command.Split(" ",2)

    $RequestComp="$Program __complete $Arguments"
    __chezmoi_debug "RequestComp: $RequestComp"

    # we cannot use $WordToComplete because it
    # has the wrong values if the cursor was moved
    # so use the last argument
    if ($WordToComplete -ne "" ) {
        $WordToComplete = $Arguments.Split(" ")[-1]
    }
    __chezmoi_debug "New WordToComplete: $WordToComplete"


    # Check for flag with equal sign
    $IsEqualFlag = ($WordToComplete -Like "--*=*" )
    if ( $IsEqualFlag ) {
        __chezmoi_debug "Completing equal sign flag"
        # Remove the flag part
        $Flag,$WordToComplete = $WordToComplete.Split("=",2)
    }

    if ( $WordToComplete -eq "" -And ( -Not $IsEqualFlag )) {
        # If the last parameter is complete (there is a space following it)
        # We add an extra empty parameter so we can indicate this to the go method.
        __chezmoi_debug "Adding extra empty parameter"
        # PowerShell 7.2+ changed the way how the arguments are passed to executables,
        # so for pre-7.2 or when Legacy argument passing is enabled we need to use
        # `"`" to pass an empty argument, a "" or '' does not work!!!
        if ($PSVersionTable.PsVersion -lt [version]'7.2.0' -or
            ($PSVersionTable.PsVersion -lt [version]'7.3.0' -and -not [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -or
            (($PSVersionTable.PsVersion -ge [version]'7.3.0' -or [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -and
              $PSNativeCommandArgumentPassing -eq 'Legacy')) {
             $RequestComp="$RequestComp" + ' `"`"'
        } else {
             $RequestComp="$RequestComp" + ' ""'
        }
    }

    __chezmoi_debug "Calling $RequestComp"
    # First disable ActiveHelp which is not supported for Powershell
    ${env:CHEZMOI_ACTIVE_HELP}=0

    #call the command store the output in $out and redirect stderr and stdout to null
    # $Out is an array contains each line per element
    Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null

    # get directive from last line
    [int]$Directive = $Out[-1].TrimStart(':')
    if ($Directive -eq "") {
        # There is no directive specified
        $Directive = 0
    }
    __chezmoi_debug "The completion directive is: $Directive"

    # remove directive (last element) from out
    $Out = $Out | Where-Object { $_ -ne $Out[-1] }
    __chezmoi_debug "The completions are: $Out"

    if (($Directive -band $ShellCompDirectiveError) -ne 0 ) {
        # Error code.  No completion.
        __chezmoi_debug "Received error from custom completion go code"
        return
    }

    $Longest = 0
    [Array]$Values = $Out | ForEach-Object {
        #Split the output in name and description
        $Name, $Description = $_.Split("`t",2)
        __chezmoi_debug "Name: $Name Description: $Description"

        # Look for the longest completion so that we can format things nicely
        if ($Longest -lt $Name.Length) {
            $Longest = $Name.Length
        }

        # Set the description to a one space string if there is none set.
        # This is needed because the CompletionResult does not accept an empty string as argument
        if (-Not $Description) {
            $Description = " "
        }
        New-Object -TypeName PSCustomObject -Property @{
            Name = "$Name"
            Description = "$Description"
        }
    }


    $Space = " "
    if (($Directive -band $ShellCompDirectiveNoSpace) -ne 0 ) {
        # remove the space here
        __chezmoi_debug "ShellCompDirectiveNoSpace is called"
        $Space = ""
    }

    if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or
       (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 ))  {
        __chezmoi_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported"

        # return here to prevent the completion of the extensions
        return
    }

    $Values = $Values | Where-Object {
        # filter the result
        $_.Name -like "$WordToComplete*"

        # Join the flag back if we have an equal sign flag
        if ( $IsEqualFlag ) {
            __chezmoi_debug "Join the equal sign flag back to the completion value"
            $_.Name = $Flag + "=" + $_.Name
        }
    }

    # we sort the values in ascending order by name if keep order isn't passed
    if (($Directive -band $ShellCompDirectiveKeepOrder) -eq 0 ) {
        $Values = $Values | Sort-Object -Property Name
    }

    if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) {
        __chezmoi_debug "ShellCompDirectiveNoFileComp is called"

        if ($Values.Length -eq 0) {
            # Just print an empty string here so the
            # shell does not start to complete paths.
            # We cannot use CompletionResult here because
            # it does not accept an empty string as argument.
            ""
            return
        }
    }

    # Get the current mode
    $Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function
    __chezmoi_debug "Mode: $Mode"

    $Values | ForEach-Object {

        # store temporary because switch will overwrite $_
        $comp = $_

        # PowerShell supports three different completion modes
        # - TabCompleteNext (default windows style - on each key press the next option is displayed)
        # - Complete (works like bash)
        # - MenuComplete (works like zsh)
        # You set the mode with Set-PSReadLineKeyHandler -Key Tab -Function <mode>

        # CompletionResult Arguments:
        # 1) CompletionText text to be used as the auto completion result
        # 2) ListItemText   text to be displayed in the suggestion list
        # 3) ResultType     type of completion result
        # 4) ToolTip        text for the tooltip with details about the object

        switch ($Mode) {

            # bash like
            "Complete" {

                if ($Values.Length -eq 1) {
                    __chezmoi_debug "Only one completion left"

                    # insert space after value
                    $CompletionText = $($comp.Name | __chezmoi_escapeStringWithSpecialChars) + $Space
                    if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
                        [System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
                    } else {
                        $CompletionText
                    }

                } else {
                    # Add the proper number of spaces to align the descriptions
                    while($comp.Name.Length -lt $Longest) {
                        $comp.Name = $comp.Name + " "
                    }

                    # Check for empty description and only add parentheses if needed
                    if ($($comp.Description) -eq " " ) {
                        $Description = ""
                    } else {
                        $Description = "  ($($comp.Description))"
                    }

                    $CompletionText = "$($comp.Name)$Description"
                    if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
                        [System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)")
                    } else {
                        $CompletionText
                    }
                }
             }

            # zsh like
            "MenuComplete" {
                # insert space after value
                # MenuComplete will automatically show the ToolTip of
                # the highlighted value at the bottom of the suggestions.

                $CompletionText = $($comp.Name | __chezmoi_escapeStringWithSpecialChars) + $Space
                if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
                    [System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
                } else {
                    $CompletionText
                }
            }

            # TabCompleteNext and in case we get something unknown
            Default {
                # Like MenuComplete but we don't want to add a space here because
                # the user need to press space anyway to get the completion.
                # Description will not be shown because that's not possible with TabCompleteNext

                $CompletionText = $($comp.Name | __chezmoi_escapeStringWithSpecialChars)
                if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage"){
                    [System.Management.Automation.CompletionResult]::new($CompletionText, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
                } else {
                    $CompletionText
                }
            }
        }

    }
}

Register-ArgumentCompleter -CommandName 'chezmoi' -ScriptBlock ${__chezmoiCompleterBlock}

Output of chezmoi doctor

bash
$ chezmoi doctor
RESULT   CHECK                       MESSAGE
ok       version                     v2.71.0, commit bd880914c19817b03567573e474254ac2ad7d84a, built at 2026-07-07T23:29:08Z, built by goreleaser
ok       latest-version              v2.71.0
ok       os-arch                     windows/amd64
info     build-info                  CGO_ENABLED=0, GOAMD64=v1
ok       systeminfo                  Microsoft Windows 11 Pro (10.0.26200 N/A Build 26200)
ok       go-version                  go1.26.5 (gc)
ok       executable                  ~/AppData/Local/Microsoft/WinGet/Links/chezmoi.exe
ok       upgrade-method              winget-upgrade
ok       config-file                 found ~/.config/chezmoi/chezmoi.toml, last modified 2026-07-12T11:53:03-07:00
ok       source-dir                  ~/.local/share/chezmoi is a git working tree (clean)
ok       suspicious-entries          no suspicious entries
ok       working-tree                ~/.local/share/chezmoi is a git working tree (clean)
ok       dest-dir                    ~ is a directory
ok       symlink                     created symlink from .new-name to .old-name
ok       cd-command                  found C:/WINDOWS/system32/cmd.exe
ok       cd-args                     'C:\\WINDOWS\\system32\\cmd.exe'
info     diff-command                not set
ok       edit-command                found C:/WINDOWS/vim.bat
ok       edit-args                   vim
ok       git-command                 found ~/AppData/Local/Programs/Git/cmd/git.exe, version 2.55.0
ok       merge-command               found C:/WINDOWS/vimdiff.bat
ok       shell-command               found C:/WINDOWS/system32/cmd.exe
ok       shell-args                  'C:\\WINDOWS\\system32\\cmd.exe'
info     age-command                 age not found in $PATH
ok       gpg-command                 found ~/AppData/Local/Programs/Git/usr/bin/gpg.exe, version 2.4.9
info     pinentry-command            not set
info     1password-command           op not found in $PATH
info     bitwarden-command           bw not found in $PATH
info     bitwarden-secrets-command   bws not found in $PATH
info     dashlane-command            dcli not found in $PATH
info     doppler-command             doppler not found in $PATH
info     gopass-command              gopass not found in $PATH
info     keepassxc-command           keepassxc-cli not found in $PATH
info     keepassxc-db                not set
info     keeper-command              keeper not found in $PATH
info     lastpass-command            lpass not found in $PATH
info     pass-command                pass not found in $PATH
info     passhole-command            ph not found in $PATH
info     protonpass-command          pass-cli not found in $PATH
info     rbw-command                 rbw not found in $PATH
info     vault-command               vault not found in $PATH
info     secret-command              not set

Additional context

This appears to apply to chezmoi: Even though it’s not documented as a requirement, it is the correct choice when:

  • Completing arguments for an external executable
  • The executable uses non‑PowerShell quoting rules
  • The executable expects POSIX‑style flags or subcommands
  • The completer needs the raw text exactly as typed