Where does the bug appear (feature/product)?
Cursor IDE
Describe the Bug
When the last thing a PowerShell command produces is an object rather than text, the rendering of that object never reaches the agent. The tool result is an empty Command output block with exit code 0 and no warning.
Get-Location is the entire reproduction. It is one token, it always prints a three-line table in a terminal, and the agent receives nothing.
Two things make this worse than a missing line of text.
The loss is silent, and an empty result is a plausible answer. Nothing distinguishes “the capture dropped this” from “the command printed nothing”, so an agent reads the empty block as a real result and carries on. A capture failure that presents itself as a successful empty result is worse than an error, because an error would have been retried.
Anything printed after the dropped block goes with it. Write-Output "before"; Get-Location; Write-Output "after" returns only before. So a single object-returning command in the middle of a compound command silently truncates the rest of that command’s output.
Everything below was measured on 3.14.7 today, each command issued on its own through the agent’s shell tool.
| Command | Captured output |
|---|---|
Get-Location |
Empty, exit code 0. Repeated three times, empty every time |
Get-Location; Get-Location |
Empty. Both tables lost |
Write-Output "before"; Get-Location; Write-Output "after" |
before only. Both the table and after lost |
Nothing here is peculiar to Get-Location. Two piped shapes behave identically: Get-Content .cursorignore | Measure-Object -Line and Get-ChildItem .cursor/hooks | Select-Object Name, Length each return empty as well.
It is not the table, the formatter, or the host. The same objects come back in full through every explicit terminator, including the two that render exactly the way the implicit path does. Each row below is Get-Location piped into the named terminator:
| Terminator | Captured output |
|---|---|
Out-Default |
The full three-line table, plus anything printed after it |
Out-Host |
The full three-line table, plus anything printed after it |
Format-Table |
The full three-line table |
Format-List |
All four properties |
Out-String |
The full three-line table |
Select-Object -ExpandProperty Path |
The path |
Get-Location and Get-Location | Out-Default produce identical bytes through identical code. One is lost and one is not. The only difference is whether the pipeline is terminated in the script or left for PowerShell to terminate implicitly.
Time after the command is the other variable. Giving the command something to do afterwards rescues it, and the same delay before it does nothing:
| Command | Captured output |
|---|---|
Write-Output "before"; Get-Location; Start-Sleep -Milliseconds 100; Write-Output "after" |
before only |
Write-Output "before"; Get-Location; Start-Sleep -Milliseconds 400; Write-Output "after" |
All three, in order |
Start-Sleep -Milliseconds 500; Get-Location |
Empty |
The multi-property case behaves the same way: Get-ChildItem .cursor/hooks | Select-Object Name, Length; Start-Sleep -Milliseconds 600; Write-Output "done" returns the full six-row table followed by done.
The threshold sits between 100 ms and 400 ms. That also explains why the bug is invisible on anything large: Get-ChildItem C:\Windows\System32 | Select-Object Name, Length returned all 4953 lines, complete down to the final ztrace_maps.dll. Small results are the ones that vanish, which is exactly the class of command an agent uses to check a fact.
This is not confined to what the model sees. Running Write-Output "start-marker"; Get-Location; Write-Output "end-marker" as a background command, the terminal output file Cursor writes to disk contains start-marker and then nothing — no table, no end-marker, followed by exit_code: 0. The same command with Start-Sleep -Seconds 8 before end-marker records all of it. There is no channel where the dropped text survives.
Steps to Reproduce
Confirmed on 3.14.7 (Windows, Windows PowerShell 5.1). Legacy Terminal Tool is off: this is the current terminal, not the legacy one. No hooks or extensions are needed.
- Open any workspace on Windows with PowerShell as the agent shell.
- Ask the agent to run
Get-Location. → The tool result is an empty output block with exit code 0. The agent has no way to tell this from a command that printed nothing. - Ask it to run
Get-Location | Out-Default. → The full table comes back. This is the control: same rendering, same cmdlet, named explicitly. - Ask it to run
Write-Output "before"; Get-Location; Write-Output "after". → Onlybeforeis returned. This is the part that corrupts a script’s output rather than merely losing it. - Ask it to run the same command with
Start-Sleep -Milliseconds 400inserted beforeWrite-Output "after". → All three arrive, in the right order.
Measure-Object -Line and a multi-property Select-Object behave identically at every step, so any object-returning command will do; Get-Location is just the shortest.
Expected Behavior
- The generated script should not be able to exit with output still unrendered. As far as I can tell the fix is in the script Cursor writes rather than in the collector (see below). Terminating the user’s command explicitly, or flushing before the script’s final
exit, would fix every case above at once. - Failing that, an empty capture should not be presented as the command’s output. Silence is what makes this dangerous. An agent that is told the capture may be incomplete will reissue the command or report; an agent handed an empty block will accept it as truth and build on it.
Operating System
Windows 10/11
Version Information
Version: 3.14.7 (system setup)
VS Code Extension API: 1.128.0
Commit: a758f2241ca99fecf380180b6cbdbbce0f1f42c0
Date: 2026-07-30T06:41:34.009Z
Layout: IDE
Build Type: Stable
Release Track: Default
Electron: 40.10.3
Chromium: 144.0.7559.236
Node.js: 24.15.0
V8: 14.4.258.32-electron.0
xterm.js: 6.1.0-beta.291
OS: Windows_NT x64 10.0.26200
Shell: Windows PowerShell 5.1.26100.8655, which is what the agent resolves to on this machine. Legacy Terminal Tool: off. Run Mode: Allowlist.
For AI issues: which model did you use?
Not model-specific — this is terminal output capture rather than model behavior. Reproduced across separate sessions and models.
For AI issues: add Request ID with privacy disabled
N/A
Additional Information
Where this is not. All of the following is in resources/app/extensions/cursor-agent-exec/dist/main.js (3.14.7). I went in expecting a collector that stops reading too early, and it is worth saying plainly that it does not. Output is drained to the end of the stream, the drain is awaited, and the capture is finished by close rather than by exit:
async function Ke(e,t,n){if(e)for await(const r of e)await n.push(t,r)}
k.on("exit",(e,t)=>{b=e;const r=n?.closeTimeout??5e3;r>0&&(B=setTimeout(()=>{console.warn(`[shell-exec] Close event did not fire within ${r}ms after exit. ...`),R(!1)},r))}),
k.on("close",async()=>{await R(!0)})
R(!0) awaits waitForOutput() and then flush() before writing the exit event and closing. There is even a 5 s closeTimeout fallback for a close that never arrives. This is the correct shape, and it means the bytes are not being dropped on the way in — they are never written to the pipe.
What the shell tool actually does on Windows, which is the part that explains the bug. It is not a persistent terminal with shell integration markers. Every command gets a freshly generated .ps1 file, run as a new process with stdout and stderr on pipes:
`\n${e}\n\nSet-Location '${t}'\n\n# Execute user command\n${n}\n$COMMAND_EXIT_CODE = $LASTEXITCODE\n\n\nfunction Dump-PowerShellState {\n ... }\n\nDump-PowerShellState -OutputFile "${r}"\n\nexit $COMMAND_EXIT_CODE\n`
const w=["-ExecutionPolicy","Bypass"];i||w.push("-NonInteractive"),w.push("-File",y);
The user’s command is dropped in as a bare statement, and the script ends with exit $COMMAND_EXIT_CODE. (This also accounts for the ~2 s floor on every agent shell command: a new PowerShell process and a full state dump per invocation.)
Mechanism. Inference from here, but tightly constrained by the measurements. A bare statement in a -File script does not go through an in-script Out-Default; its objects leave the pipeline and are rendered by the process-level output writer, which buffers. The script’s terminating exit then kills the process while that buffer is still pending, so the bytes are discarded before they are ever written. Naming any terminator — Out-Default, Out-Host, Format-Table, Out-String — moves the rendering inside the script, where it is written immediately, which is why all of them survive. A few hundred milliseconds of subsequent work lets the buffer’s own flush fire first, and a large result exceeds it and is written continuously.
The decisive evidence that this is upstream of the collector rather than a positional truncation in it: after sits in exactly the same final position in both Write-Output "before"; Get-Location; Write-Output "after" and Write-Output "before"; Get-Location | Out-Default; Write-Output "after". Same bytes, same place, same size. It survives in the second and dies in the first. A collector dropping a tail could not tell those apart. What distinguishes them is that in the first case PowerShell must emit output in order, so after is queued behind a render that never happens and dies with it.
If that reading is right the fix is cheap and entirely on Cursor’s side, in the template above: pipe the user command through an explicit Out-Default, or flush before exiting, or let the script end without a terminating exit.
One thing worth checking while you are in there. ze() has two paths, and only one of them keeps the guarantees described above. When bufferOutputEvents is falsy it attaches plain data listeners, does not await the writes, and returns no-ops:
if(!e.bufferOutputEvents){const t=(t,n)=>{e.writable.write({type:t,data:n}).catch(()=>{})};
e.stdout?.on("data",e=>{t("stdout",e)}),e.stderr?.on("data",e=>{t("stderr",e)});
return{async waitForOutput(){},async flush(){}}}
On that path R(!0) awaits nothing and l.close() follows immediately, so a write still in flight has nothing holding it. That is not the bug reported here — this one is deterministic and selected by output shape, not by position — but it is a plausible source of the intermittent tail losses in the older threads below, and it is the one place in this file where output has no delivery guarantee.
The workaround, as a rule entry. For anyone who wants the defense before this is fixed, this is the whole of it, small enough to paste into a project’s rules file:
## Object Output
Never let a PowerShell command end by emitting an object. The rendering is lost
silently, and takes anything printed after it. Make the last stage produce text:
`-ExpandProperty` for one property, `-join` to build the line, or `| Out-String`
when neither fits.
Bad: Get-ChildItem $dir | Select-Object Name, Length
Good: Get-ChildItem $dir | ForEach-Object { $_.Name, $_.Length -join " " }
Bad: Get-Content $file | Measure-Object -Line
Good: Get-Content $file | Measure-Object -Line | Select-Object -ExpandProperty Lines
One working command proves nothing: large output survives, so only small, fast
results vanish. If a command returns nothing where something was expected,
suspect this before concluding the result is empty, and reissue it in string form.
Related threads. There is an existing family of reports about the tail of a command’s output going missing, all on Windows, all now closed with the team unable to reproduce:
I am filing separately rather than reviving those because the reproduction is materially different and much easier to act on. Those are about the last line of plain text disappearing intermittently, on the Legacy Terminal Tool, in scripts that were hard to reduce. This one is on the current terminal, loses an entire result rather than a line, is selected by the shape of the output rather than by position, is deterministic, and reduces to a single token with a one-word control (Get-Location against Get-Location | Out-Default).
Separately, that post is not the same bug. I had assumed it would be before reading the code. It is worth mentioning only for the contrast: the hook collector in extensionHostProcess.js returns on the first exit event and drops stdout still in flight, which is exactly the mistake the shell collector above avoids. The correct pattern already exists in the codebase; it just is not used in both places.
Does this stop you from using Cursor
No - Cursor works, but with this issue