
April 14, 20269 min read

Every guide says a fancy zsh prompt is why your AI coding agent cannot read the terminal. So I deleted mine. Then I deleted bash too, and the commands still were not running.
The agent told me the command succeeded. Exit code 0, no output. Exit 0 with nothing printed usually just means “ran fine, nothing to say,” so I let it keep going. A few steps later nothing downstream made sense, so I backed up and asked it to do the simplest possible thing: write the date to a temp file.
execute_bash: date > /tmp/kiro_test.txt && echo "wrote it"
-> output: ""
-> exit code: 0Exit 0 again. No “wrote it,” but fine, I redirected stdout, whatever. Then I read the file back.
read_file /tmp/kiro_test.txt
-> "Path does not exist"The file was never created. The command returned exit 0 and it had not run at all. That was the moment the whole thing flipped for me. I had spent most of a weekend treating this as “the agent cannot read my terminal output.” It was not that. The agent was being handed a success code for a command that never happened.
I build CLI tools that agents drive, like Coco and Strut, so a coding agent that quietly reports success for commands it never ran is roughly my worst case. Here is how I got there, what I tried, and where the trail actually led.
Kiro is a VS Code based agentic editor, and like Copilot, Cursor, and Cline, it runs your commands through something called terminal shell integration. The idea: the shell emits invisible marker sequences around each command so the editor knows where the output starts, where it stops, and what exit code came back. In zsh, the integration registers hooks in the precmd_functions and preexec_functions arrays to print those markers at the right moments.
Here is the catch. If you run a fancy prompt (oh-my-zsh, powerlevel10k, starship, spaceship), it hooks those exact same arrays. It writes its own escape sequences too: terminal title updates, async git status, a right-side prompt. Those writes land in the byte stream between the editor’s markers and smear the boundary. The editor sees its start marker, goes looking for the end, and finds garbage or nothing. Empty output.
This is the official story. Kiro’s own troubleshooting docs name Oh My Posh and Powerlevel10k/9k as common culprits. A StackOverflow answer pins the right-side prompt (RPROMPT) specifically. It is real, it is well documented, and it is good advice. So I took it.
My setup was not minimal. Oh-my-zsh, the spaceship theme, autosuggestions sourced twice, nvm, conda, direnv, and a pile of duplicate exports I am not proud of. Seven hooks fired before Kiro’s integration even got a turn. So I went down the list and pulled one thread at a time.
ZSH_THEME to kill spaceship. Still empty.Every change, same result: exit 0, empty output, zero commands captured. The only thing that ever moved the needle was going nuclear. I gave the agent shell an early exit before oh-my-zsh ever loaded.
if [[ "$TERM_PROGRAM" == "kiro" ]]; then
export PROMPT='%~ %# '
export RPROMPT=''
ZSH_THEME=""
# load PATH, nvm, aliases, completions here
return 0 # stop before oh-my-zsh and every competing hook
fiThat worked, sort of. I went from zero commands captured to maybe six in ten. Which is the part I want to be fair about: if you are sitting at total blackout right now, this block is worth doing. It is the difference between unusable and usable, and it costs you nothing but a plain prompt.
But read that number again. Six in ten. With zero hooks left. A bare prompt. The cleanest shell I have ever run. If competing hooks were the whole story, removing all of them should have taken me to ten in ten. There were none left to remove, and it was still dropping commands. Something else was going on.
So I stopped trusting my own shell and went looking for a floor. I ran a bare bash subshell with no startup files at all:
/bin/bash --norc --noprofile -c 'echo "from bash"'
-> output: ""Empty. No rc files, no theme, no hooks, not even zsh, and still empty. At that point “your shell config is the problem” had run out of road. There was no config.
Which brought me back to the file-write test from the top of this post, and I started running it on purpose. The results reframed the entire problem:
this_command_does_not_exist_xyz returned exit 0. It should be 127, command not found.false returned exit 0. It exists to return 1.exit 1 returned exit 0.This is the thing nobody else in the threads I read had pinned down. During these blackouts, the problem is not that the agent cannot read the output. The command is not running. The tool reports exit 0 for something it never delivered to a shell. Everyone, including me, had been asking “why cannot the agent see the output?” The real question is “why is the agent not running the command?”
Three things convinced me this lives in the editor, not in my dotfiles.
First, Kiro has a second way to run commands: a background process tool (control_bash_process plus get_process_output) that reads the terminal buffer directly instead of leaning on shell-integration markers. In the same dead session where execute_bash returned nothing, that path captured output every single time. Different reader, zero failures. The terminal was fine. One specific tool’s way of talking to it was not.
Second, I could unstick the broken tool on demand. Start a background process and immediately stop it, and execute_bash would come back to life for three to five commands before going dark again. You do not get behavior like that from a shell config. You get it from shared state in a terminal pool that briefly reconciles when you poke it.
Third, and this is the one I did not even find myself: the person who filed the clearest version of this bug on Kiro’s tracker (issue #9483) reverted Kiro from 1.0.0 back to 0.11.133 and the blackouts vanished. Same machine, same shell, older editor, no bug. That is about as clean a “this is a platform regression” signal as you are going to get.
And it was not just one report. In the days after 1.0.0 landed, several of these showed up on the tracker at once (#9483, #9501, #9535, #9564, #9604, #9628), all on the same version. That is more corroboration that the trigger is the release, not any one person’s setup.
My working model has three failure modes stacked on top of each other, and they are not equally fixable.
.zshrc. This is what the official advice addresses. Kiro issue #4833 describes it precisely, and a controlled bisect in issue #9564 narrows it to the right-prompt (RPROMPT) markers stepping on the command-start and exit-code markers on 1.0.0. It is not only prompts, either: a report on the tracker pins the same swallowed-output behavior on the Python extension auto-activating a virtualenv and writing into the same stream (issue #9663).sendText calls scrollToBottom on a terminal whose xterm instance has not finished rendering, reads dimensions off something undefined, and throws. The exception fires before the command text is ever written to the shell, so nothing runs and you still get exit 0. That is the “command never executed” case, and it is the editor’s code, not your environment.Cleaning your shell only touches the first one. It is worth doing, and it is not enough. The other two are the editor’s to fix.
The early-return block above is still the best user-side move. It eliminates the one mode you control and gets you from zero to mostly working. The honest cost is real, though: there is no way to apply it to only the agent’s terminal. Both the agent terminal and the one you open by hand report TERM_PROGRAM=kiro and VSCODE_SHELL_INTEGRATION=1, with nothing in Kiro to tell them apart. So the clean, boring prompt you give the agent is the same prompt you get yourself. You trade your nice setup in every Kiro terminal to make the agent reliable.
If you are on 1.0.0 and it is genuinely unusable, the downgrade that worked on #9483 is a legitimate stopgap until there is a fix. And if you hit a blackout mid-session, starting and stopping a throwaway background process will buy you a few working commands.
The architecture underneath all of this is the actual problem. Driving an interactive shell session, the one tuned for humans with prompts and colors and git status, for programmatic command execution is fragile by design. The agent does not need any of that. It needs clean input and output boundaries and a deterministic exit code.
Here is the part that should make this fixable: upstream VS Code has already built the pieces. A few moves would help, roughly in order of how much:
execute_bash read output the same way the background-process tool does, since that path already works every time.VSCODE_COPILOT_TERMINAL, so we could strip the noisy bits for the agent and keep our prompt for ourselves.Both of those last two already exist a layer up the stack. The catch is that a fork has to pick them up. I tried VS Code’s older automationProfile setting, the one built for non-interactive task shells, and Kiro ignores it for agent terminals in favor of its own spawning logic. So the levers exist; they are just not wired into Kiro’s agent yet.
Some of the mechanism is on the table now: the sendText crash in #9628 explains the “never ran” case, and the RPROMPT marker corruption in #9564 explains a chunk of the “ran but no output” case. What I still want to know is whether they share a single root cause in the 1.0.0 terminal rewrite, and whether pointing the agent at the background-process reader, the one path that never failed for me, would sidestep both. If you have spent more time inside Kiro’s terminal manager than I have, I want to hear it. The longer debugging notes are on #4011 and #9483 if you are fighting the same thing.
Discussion
Comments are powered by Disqus. Sign in once, comment anywhere.