#7737·starship

PowerShell transient prompt corrupts output after multiline command entry

Author: WitnzCreated Sep 16, 2026Updated Sep 16, 2026
Labels🐛 bug

Current Behavior

With Enable-TransientPrompt active and a prompt that renders across two rows, accepting a command that was typed across continuation lines leaves the continuation rows on screen. Command output then gets written over the top of them.

Typing this as a multiline block:

Write-Host "test line 1"
Write-Host "test line 2"
Write-Host "test line 3"

gives:

❯ Write-Host "test line 1"
∙ Write-Host "test line 2"
∙ Write-Host "test line 3"
test line 1t "test line 2"
test line 2t "test line 3"
test line 3

The t "test line 2" and t "test line 3" fragments are the tail ends of the continuation rows that were never cleared. The output of the first two Write-Host calls lands on those rows and only partially covers them.

Single line commands are fine. This renders correctly with transient prompt enabled:

1..3 | % { Write-Host "test $_" }
test 1
test 2
test 3

The error also accumulates. Each subsequent multiline command shifts the prompt one row further up the screen, so after a few commands it is walking back through scrollback and overwriting earlier output.

Expected Behavior

The continuation rows should be cleared before command output is written, so the multiline block gives:

test line 1
test line 2
test line 3

which is what happens with Disable-TransientPrompt, or with a single row prompt.

Additional context/Screenshots

What I ruled out while narrowing this down:

  • Not the transient prompt content. Reproduces with function global:Invoke-Starship-TransientFunction { '> ' }, so the ANSI sequences and Nerd Font glyphs in the character module are not involved.
  • Not add_newline. Reproduces with it set to false.
  • Not fill. Reproduces with $fill removed from the format string, so terminal width padding and wrapping are not involved.
  • Not ExtraPromptLineCount on its own. Setting it to 0 while keeping the real two row prompt does not help.
  • Not PSReadLine or the shell in general. function prompt { "PS> " } with Set-PSReadLineOption -ExtraPromptLineCount 0 renders multiline commands correctly with transient prompt enabled.
  • Not my profile. Reproduces with the prompt and transient prompt as the only variables.

starship prompt splits into three elements, so the live prompt occupies two rendered rows:

[[]]
[[   ~ ·············································  23:35:46 ]]
[[❯ ]]

The one thing common to every failing case is that the live prompt is taller than the transient prompt it collapses into. Content does not seem to matter, only the row count difference.

PSReadLine is 2.4.5.

Possible Solution

The transient path calls PSConsoleReadLine::InvokePrompt() before PSConsoleReadLine::AcceptLine(), while the input buffer is still live. PSReadLine places the input region relative to the prompt height it is handed. When the transient prompt is shorter than the live prompt, the input region moves up by the difference and the rows at the bottom of the original region are left behind.

Matching the transient prompt height to the live prompt height fixes it completely. This is what I am running as a workaround:

function global:Invoke-Starship-TransientFunction {
    $character = & starship module character

    $line = $null
    $cursor = $null

    try {
        [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState(
            [ref]$line,
            [ref]$cursor
        )
    }
    catch {
        return "`n" + $character
    }

    if ($line -and $line.Contains("`n")) {
        return "`n" + $character
    }

    return $character
}

Padding only when the buffer contains continuation lines keeps single line commands on a compact one row transient prompt, which is the whole point of the feature.

A real fix probably wants to clear the full height of the previous input region before repainting, rather than depending on the two prompts happening to be the same height. Computing the row delta between the live and transient prompts and clearing that many extra rows would also work.

Environment

  • Starship version: 1.26.0
  • pwsh version: PowerShell 7.6.6
  • Operating system: Windows 10.0.26200
  • Terminal emulator: WezTerm 20260912-133823-2afb8364
  • Git Commit Hash: fca92d8
  • Branch/Tag: main
  • Rust Version: rustc 1.96.0 (ac68faa20 2026-05-25)
  • Rust channel: stable-x86_64-pc-windows-msvc release
  • Build Time: 2026-06-28 17:05:55 +00:00

Relevant Shell Configuration

<unknown config>

Starship Configuration

# ~/.config/starship.toml
# Bubble / capsule prompt matching the zsh reference.
# Each segment is a rounded pill: left-cap + content on grey bg (238) + right-cap.
# Nerd Font (v3) required for the powerline caps and icons.

add_newline = false
scan_timeout = 100

format = """
$os\
$directory\
$git_branch\
$git_status\
$git_state\
$git_metrics\
$fill\
$status\
$jobs\
$nodejs\
$python\
$golang\
$rust\
$java\
$kotlin\
$ruby\
$docker_context\
$aws\
$username\
$hostname\
$cmd_duration\
$time\
$line_break\
$character\
"""

[fill]
symbol = '·'
style = 'fg:244'

[line_break]
disabled = false

[character]
success_symbol = "[❯](bold fg:76)"
error_symbol = "[❯](bold fg:196)"
vicmd_symbol = "[❮](bold fg:76)"

[os]
disabled = false
format = "[](fg:236)[ $symbol ](fg:255 bg:236)[](fg:248 bg:236)"

[os.symbols]
Windows = ""

[directory]
truncation_length = 3
truncation_symbol = "…/"
format = "[ $path ](fg:39 bg:236)[](fg:236)"

[git_branch]
symbol = " "
format = "[](fg:236)[ $symbol$branch ](fg:76 bg:236)[](fg:236)"
always_show_remote = true

[git_status]
format = '([](fg:236)[ $all_status$ahead_behind ](fg:220 bg:236)[](fg:236))'
conflicted = "="
ahead = "⇡${count}"
behind = "⇣${count}"
diverged = "⇕⇡${ahead_count}⇣${behind_count}"
up_to_date = ""
untracked = "?${count}"
stashed = ""
modified = "!${count}"
staged = "+${count}"
renamed = ">${count}"
deleted = "✘${count}"

[git_metrics]
format = '([](fg:236)[ +$added -$deleted ](fg:66 bg:236)[](fg:236))'
disabled = false

[git_state]
format = '([](fg:236)[ $state( $progress_current/$progress_total ) ](fg:178 bg:236)[](fg:236))'

[status]
disabled = false
format = "([](fg:236)[ $symbol$status ](fg:160 bg:236)[](fg:236))"
symbol = "✘"
success_symbol = ""
not_executable_symbol = ""
not_found_symbol = ""
sigint_symbol = "✘"
signal_symbol = "⚡"

[jobs]
number_threshold = 1
symbol_threshold = 1
symbol = "✦"
format = "([](fg:236)[ $symbol$number ](fg:37 bg:236)[](fg:236))"

[nodejs]
disabled = false
format = "([](fg:236)[  $version ](fg:70 bg:236)[](fg:236))"

[python]
disabled = false
format = "([](fg:236)[  $version ](fg:37 bg:236)[](fg:236))"

[golang]
disabled = false
format = "([](fg:236)[  $version ](fg:37 bg:236)[](fg:236))"

[rust]
disabled = false
format = "([](fg:236)[  $version ](fg:37 bg:236)[](fg:236))"

[java]
disabled = false
format = "([](fg:236)[  $version ](fg:32 bg:236)[](fg:236))"

[kotlin]
disabled = false
format = "([](fg:236)[  $version ](fg:32 bg:236)[](fg:236))"

[ruby]
disabled = false
format = "([](fg:236)[  $version ](fg:168 bg:236)[](fg:236))"

[docker_context]
disabled = false
format = "([](fg:236)[  $context ](fg:33 bg:236)[](fg:236))"

[aws]
disabled = false
format = "([](fg:236)[  $profile ](fg:208 bg:236)[](fg:236))"

[cmd_duration]
min_time = 0
format = "[](fg:236)[ took $duration ](fg:248 bg:236)[](fg:236)"
disabled = false

[time]
disabled = false
format = "[](fg:236)[  $time ](fg:250 bg:236)[](fg:236)"
use_12hr = false
time_format = "%H:%M:%S"

[username]
format = "([](fg:236)[ $user ](fg:180 bg:236)[](fg:236))"
show_always = false
disabled = false

[hostname]
ssh_only = true
format = "([](fg:236)[ @$hostname ](fg:180 bg:236)[](fg:236))"
disabled = false

[conda]
disabled = true