Summary
In boo ui, libghostty's VT stream parser logs unimplemented sequences at info level under the stream scope (e.g. info(stream): OSC 1 (change icon) received and ignored). The UI renders in-process in the user's terminal (it hosts its own stream parser in src/ui.zig and refuses to start without a TTY), with stderr inherited — so those log lines paint directly over the rendered viewport and corrupt it. C-a l (redraw) repaints boo's clean screen model and restores it; the underlying session is never actually affected, and boo peek stays clean (confirming this is stderr output, not the screen model).
Powerlevel10k is a common trigger: it emits an OSC 1 (set icon) on every prompt and on preexec, so the pane fills with ... received and ignored icon=~ / icon=<cmd> lines during normal use. Running e.g. cat ~/.zshrc reliably reproduces it.
startDaemon (src/main.zig) redirects the daemon's stderr to BOO_LOG or /dev/null:
// Detach stdio. Keep stderr pointed at BOO_LOG if set so std.log output is preserved.
const devnull = posix.open("/dev/null", ...);
posix.dup2(devnull, 0); posix.dup2(devnull, 1);
if (posix.getenv("BOO_LOG")) |log_path| { ... posix.dup2(fd, 2); } else { posix.dup2(devnull, 2); }
boo ui runs via ui.run(...) without any such redirect, so the same info(stream) logs go straight to the user's terminal and over the rendered pane.
Environment
- macOS (Darwin 25.5.0), arm64; iTerm2 3.6.11; zsh + oh-my-zsh + Powerlevel10k
Verified: this is not an optimization-level issue
I built v0.5.18 from source at both Debug and ReleaseSafe (the config release.yml ships) and drove a session that emits a single OSC 1, capturing BOO_LOG:
| build |
info(stream) lines emitted |
| ReleaseSafe (release config) |
1 |
| Debug |
1 |
So libghostty-vt emits these info logs regardless of optimize mode — building release does not fix it (releases already use ReleaseSafe). The fix has to filter the log.
Reproduce
Interactive (shows the corruption): with a Powerlevel10k prompt, boo ui → cat ~/.zshrc → the pane fills with info(stream) lines → C-a l clears it.
Headless (shows the root-cause log line), via Docker:
FROM debian:bookworm-slim
ARG BOO_ARCH=aarch64 # x86_64 on Intel hosts
ARG BOO_VERSION=v0.5.18
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL -o /tmp/boo.tar.gz \
"https://github.com/coder/boo/releases/download/${BOO_VERSION}/boo-${BOO_ARCH}-linux.tar.gz" \
&& tar -xzf /tmp/boo.tar.gz -C /usr/local/bin boo && chmod +x /usr/local/bin/boo
CMD bash -c '\
export BOO_DIR=/tmp/s BOO_LOG=/tmp/boo.log; : > "$BOO_LOG"; \
boo new repro -d -- sh -c "printf \"\033]1;demo-icon\007\"; sleep 2"; sleep 3; \
echo "--- peek (parsed screen, stays clean) ---"; boo peek repro; \
echo "--- BOO_LOG (the leaked stream-scope line) ---"; cat "$BOO_LOG"; \
boo kill --all'
docker build --platform linux/arm64 -t boo-repro . && docker run --rm --platform linux/arm64 boo-repro
Output — the parser logs the OSC even though peek is clean:
--- peek (parsed screen, stays clean) ---
--- BOO_LOG (the leaked stream-scope line) ---
info(stream): OSC 1 (change icon) received and ignored icon=demo-icon
(The container can't render the visual corruption — that needs an interactive boo ui on a real TTY, since a detached -d daemon sends stderr to /dev/null — but it deterministically reproduces the log line that leaks into the viewport.)
Suggested fix
boo installs no std_options, so logging follows Zig defaults. A logFn that drops info-and-below from the stream scope fixes it while leaving boo's own .ui/.daemon/.window logs intact. I've opened a draft PR with this change (verified zig fmt clean, zig build test passes, and the emitted info(stream) count drops 1 → 0 at ReleaseSafe).
I've created a draft PR that fixed the problem, but I still need to research if this is the best approach, since I'm not fully fluent in this stack
Workaround for users
oh-my-zsh DISABLE_AUTO_TITLE="true" (Powerlevel10k may also need its own title hooks disabled) removes the OSC-1 trigger — but any program emitting an unhandled sequence still corrupts boo ui until the log is filtered.
Thanks for building boo — the libghostty-backed "screen comes back exactly as a human would see it" model is genuinely great to use, and the peek/send/wait automation surface is lovely. Happy to adjust the fix to whatever shape you prefer.
Investigation note: I used an LLM (Claude) to help investigate and draft this report (If it's helpful, I can add the session history.) then verified every claim by building boo from source and testing — including disproving two of my own initial hypotheses (a #58 regression, and an optimization-level cause). This is a known class of bug — terminal-library log output reaching the TTY a TUI renders into; cf. opencode#6546.
Summary
In
boo ui, libghostty's VT stream parser logs unimplemented sequences atinfolevel under thestreamscope (e.g.info(stream): OSC 1 (change icon) received and ignored). The UI renders in-process in the user's terminal (it hosts its ownstreamparser insrc/ui.zigand refuses to start without a TTY), with stderr inherited — so those log lines paint directly over the rendered viewport and corrupt it.C-a l(redraw) repaints boo's clean screen model and restores it; the underlying session is never actually affected, andboo peekstays clean (confirming this is stderr output, not the screen model).Powerlevel10k is a common trigger: it emits an
OSC 1(set icon) on every prompt and onpreexec, so the pane fills with... received and ignored icon=~/icon=<cmd>lines during normal use. Running e.g.cat ~/.zshrcreliably reproduces it.startDaemon(src/main.zig) redirects the daemon's stderr toBOO_LOGor/dev/null:boo uiruns viaui.run(...)without any such redirect, so the sameinfo(stream)logs go straight to the user's terminal and over the rendered pane.Environment
Verified: this is not an optimization-level issue
I built v0.5.18 from source at both
DebugandReleaseSafe(the configrelease.ymlships) and drove a session that emits a singleOSC 1, capturingBOO_LOG:info(stream)lines emittedSo libghostty-vt emits these
infologs regardless of optimize mode — building release does not fix it (releases already useReleaseSafe). The fix has to filter the log.Reproduce
Interactive (shows the corruption): with a Powerlevel10k prompt,
boo ui→cat ~/.zshrc→ the pane fills withinfo(stream)lines →C-a lclears it.Headless (shows the root-cause log line), via Docker:
Output — the parser logs the OSC even though
peekis clean:(The container can't render the visual corruption — that needs an interactive
boo uion a real TTY, since a detached-ddaemon sends stderr to/dev/null— but it deterministically reproduces the log line that leaks into the viewport.)Suggested fix
boo installs no
std_options, so logging follows Zig defaults. AlogFnthat dropsinfo-and-below from thestreamscope fixes it while leaving boo's own.ui/.daemon/.windowlogs intact. I've opened a draft PR with this change (verifiedzig fmtclean,zig build testpasses, and the emittedinfo(stream)count drops 1 → 0 atReleaseSafe).I've created a draft PR that fixed the problem, but I still need to research if this is the best approach, since I'm not fully fluent in this stack
Workaround for users
oh-my-zsh
DISABLE_AUTO_TITLE="true"(Powerlevel10k may also need its own title hooks disabled) removes the OSC-1 trigger — but any program emitting an unhandled sequence still corruptsboo uiuntil the log is filtered.Thanks for building boo — the libghostty-backed "screen comes back exactly as a human would see it" model is genuinely great to use, and the
peek/send/waitautomation surface is lovely. Happy to adjust the fix to whatever shape you prefer.