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 $Out is empty. The generated script calls $Out[-1].TrimStart(':') without checking if $Out contains 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 a System.Char instead of a System.String. Since System.Char does not have a TrimStart method, 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"
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: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:Filtering Collision on Identical String Values:
To strip the directive (which is the last element of the output, such as
:4), the script uses: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.goinside the shell script template, the generated script assumes$Outis always a populated array with string elements:Proposed Fix
Modify the template inside
powershell_completions.goto safely check the size of the array, convert the directive to an integer defensively and strip the last item cleanly by index usingSelect-Object: