PowerShell autocompletion script fails with runtime errors on empty or single-item completions
Description
Cobra's generated PowerShell completion script fails with runtime exceptions in various edge cases due to how it parses and filters the completion output array ($Out):
Null Pointer/MethodNotFound Exceptions on Empty Outputs: If a command execution returns no output (for example, if the CLI binary crashes, fails or simply yields no completions and stdout is empty), the array
$Outis empty. The generated script calls$Out[-1].TrimStart(':')without checking if$Outcontains any elements. This throws a fatal PowerShell error:You cannot call a method on a null-valued expression.System.Char vs. String Type Confusion: PowerShell pipelines automatically unwrap arrays containing exactly one element into a scalar object (such as
System.String). In the next execution phase, querying$Out[-1]returns aSystem.Charinstead of aSystem.String. SinceSystem.Chardoes not have aTrimStartmethod, calling it throws:Method invocation failed because [System.Char] does not contain a method named 'TrimStart'.Filtering Collision on Identical String Values: To strip the directive (which is the last element of the output, such as
:4), the script uses:$Out = $Out | Where-Object { $_ -ne $Out[-1] }Instead of removing only the last item by index, this filters out all items matching the value of the last item. If a command legitimately outputs a completion item that matches the directive string (such as when a flag or argument completes to the string
":0"), it is incorrectly stripped.
Root Cause Analysis
In powershell_completions.go inside the shell script template, the generated script assumes $Out is always a populated array with string elements:
#call the command store the output in $out and redirect stderr and stdout to null
Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null
# get directive from last line
[int]$Directive = $Out[-1].TrimStart(':')
if ($Directive -eq "") {
$Directive = 0
}
# remove directive (last element) from out
$Out = $Out | Where-Object { $_ -ne $Out[-1] }
Proposed Fix
Modify the template inside powershell_completions.go to safely check the size of the array, convert the directive to an integer defensively and strip the last item cleanly by index using Select-Object:
$Directive = 0
if ($Out.Count -gt 0) {
$DirectiveStr = $Out[-1].TrimStart(':')
if ($DirectiveStr -ne "") {
$Directive = [int]$DirectiveStr
}
$Out = $Out | Select-Object -First ($Out.Count - 1)
}
__%[1]s_debug "The completion directive is: $Directive"
__%[1]s_debug "The completions are: $Out"
Source: spf13/cobra