From f4b1cc084bf4b7d2088bea3c7e703cb07d7b00ad Mon Sep 17 00:00:00 2001 From: can1357 Date: Sun, 17 May 2026 05:29:56 +0200 Subject: [PATCH] fix: guarded terminal control sequences behind isTTY checks - Skipped thinking-line writes and clears when stdout is not a TTY. - Prevented terminal title (set/push/pop) from emitting OSC/xterm escape sequences to piped or non-interactive output. - Suppressed all ProcessTerminal control writes when stdout isn't a TTY. --- packages/coding-agent/src/commit/agentic/agent.ts | 2 ++ packages/coding-agent/src/config/model-resolver.ts | 2 +- packages/coding-agent/src/utils/title-generator.ts | 3 +++ packages/tui/src/terminal.ts | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/src/commit/agentic/agent.ts b/packages/coding-agent/src/commit/agentic/agent.ts index 8ae14e79c..baad0cbe0 100644 --- a/packages/coding-agent/src/commit/agentic/agent.ts +++ b/packages/coding-agent/src/commit/agentic/agent.ts @@ -79,12 +79,14 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise }>(); const writeThinkingLine = (text: string) => { + if (!process.stdout.isTTY) return; const line = chalk.dim(`… ${text}`); process.stdout.write(`\r\x1b[2K${line}`); thinkingLineActive = true; }; const clearThinkingLine = () => { if (!thinkingLineActive) return; + if (!process.stdout.isTTY) return; process.stdout.write("\r\x1b[2K"); thinkingLineActive = false; }; diff --git a/packages/coding-agent/src/config/model-resolver.ts b/packages/coding-agent/src/config/model-resolver.ts index 0fd27c431..31602a645 100644 --- a/packages/coding-agent/src/config/model-resolver.ts +++ b/packages/coding-agent/src/config/model-resolver.ts @@ -13,7 +13,7 @@ import { modelsAreEqual, } from "@oh-my-pi/pi-ai"; import { fuzzyMatch } from "@oh-my-pi/pi-tui"; -import chalk from "chalk"; +import { logger } from "@oh-my-pi/pi-utils"; import MODEL_PRIO from "../priority.json" with { type: "json" }; import { parseThinkingLevel, resolveThinkingLevelForModel } from "../thinking"; import { isAuthenticated, kNoAuth, MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry"; diff --git a/packages/coding-agent/src/utils/title-generator.ts b/packages/coding-agent/src/utils/title-generator.ts index c87ad7a61..d0fe8a729 100644 --- a/packages/coding-agent/src/utils/title-generator.ts +++ b/packages/coding-agent/src/utils/title-generator.ts @@ -166,6 +166,7 @@ export function formatSessionTerminalTitle(sessionName: string | undefined, cwd? * Set the terminal title using OSC 0 (sets both tab and window title). Unsupported terminals ignore it. */ export function setTerminalTitle(title: string): void { + if (!process.stdout.isTTY) return; process.stdout.write(`\x1b]0;${sanitizeTerminalTitlePart(title) ?? DEFAULT_TERMINAL_TITLE}\x07`); } @@ -177,6 +178,7 @@ export function setSessionTerminalTitle(sessionName: string | undefined, cwd?: s * Save the current terminal title on terminals that support xterm window ops. */ export function pushTerminalTitle(): void { + if (!process.stdout.isTTY) return; process.stdout.write("\x1b[22;2t"); } @@ -184,5 +186,6 @@ export function pushTerminalTitle(): void { * Restore the previously saved terminal title on terminals that support xterm window ops. */ export function popTerminalTitle(): void { + if (!process.stdout.isTTY) return; process.stdout.write("\x1b[23;2t"); } diff --git a/packages/tui/src/terminal.ts b/packages/tui/src/terminal.ts index 4bfcf18fc..89ab351ec 100644 --- a/packages/tui/src/terminal.ts +++ b/packages/tui/src/terminal.ts @@ -591,6 +591,9 @@ export class ProcessTerminal implements Terminal { #safeWrite(data: string): void { if (this.#dead) return; + // Skip control sequences when stdout isn't a TTY (piped output, tests, log + // files). They serve no purpose there and would surface as visible noise. + if (!process.stdout.isTTY) return; try { process.stdout.write(data); } catch (err) {