anomalyco / anomalyco/opencode
opencode stats writes a raw cursor-up escape when output is not a terminal
@simonklee is already working on this.
Since Sep 5, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
opencode stats closes its MODEL USAGE box by printing a separator after every model and then erasing the last one with a cursor-up escape:
packages/opencode/src/cli/cmd/stats.ts:340-352
for (const [model, usage] of modelsToDisplay) {
console.log(`│ ${model.padEnd(54)} │`)
...
console.log("├────────────────────────────────────────────────────────┤")
}
// Remove last separator and add bottom border
process.stdout.write("\x1B[1A") // Move up one line
console.log("└────────────────────────────────────────────────────────┘")
\x1B[1A only erases anything on a terminal. It is written unconditionally — there is no isTTY check anywhere in the file — so as soon as stdout is not a terminal the escape becomes literal bytes in the output and the separator it was meant to overwrite survives.
Redirecting or piping the command (opencode stats > stats.txt, opencode stats | less, capturing it from a script or CI job) produces a broken box with a raw control character in it. Reproduced with the exact print sequence:
# current
│ modelA │
├────────────────────────────────────────────────────────┤
│ modelB │
├────────────────────────────────────────────────────────┤ <- should not be here
^[[1A└────────────────────────────────────────────────────────┘ <- literal ESC[1A
# expected
│ modelA │
├────────────────────────────────────────────────────────┤
│ modelB │
└────────────────────────────────────────────────────────┘
The other two boxes in this command (OVERVIEW, COST & TOKENS) do not need the trick because they have a fixed row count. Only MODEL USAGE loops.
Printing the separator before each entry except the first removes the need for the escape entirely and produces byte-identical output on a terminal.
Steps to reproduce
opencode stats --models 2 > out.txt(or pipe it anywhere that is not a TTY).cat -v out.txt— the final line begins with^[[1Aand the separator above it was never removed.
Operating System
Windows 11 (platform independent — the escape is written regardless of platform)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.