anomalyco / anomalyco/opencode

opencode stats writes a raw cursor-up escape when output is not a terminal

Open
#47,471 0 comments 0 reactions 1 assignee View on GitHub

@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
  1. opencode stats --models 2 > out.txt (or pipe it anywhere that is not a TTY).
  2. cat -v out.txt — the final line begins with ^[[1A and the separator above it was never removed.
Operating System

Windows 11 (platform independent — the escape is written regardless of platform)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.