Skip to content

Commit 49fa941

Browse files
anandgupta42claude
andauthored
Fix TUI prompt corruption by using Log instead of UI.println (#180)
* fix: replace UI.println with Log in engine bootstrap to prevent TUI prompt corruption Engine bootstrap messages (downloading uv, engine ready, etc.) were written via UI.println() which writes to stderr. In TUI mode the framework captures stderr, causing garbled text like "readyltimate-engine 0.4.0..." in the prompt input area. Switched all status messages in engine.ts to use Log.Default.info() which routes to the log file instead of stderr, keeping the TUI display clean. https://claude.ai/code/session_01MBVNhRX6XKTHqtZ6zS1i6w * chore: update bun.lock after dependency install https://claude.ai/code/session_01MBVNhRX6XKTHqtZ6zS1i6w * fix: replace console.log/error with Log.Default in TUI code to prevent prompt corruption All console.log and console.error calls in TUI components and bridge client wrote to stdout/stderr, which the TUI framework captures and renders as garbled text in the prompt input area. Replace with Log.Default.info/error which writes to the log file instead. Affected: route navigation, bootstrap, theme resolution, route changes, clipboard detection, session creation, auto-enhance, MCP toggle, workspace creation, and altimate-engine stderr. https://claude.ai/code/session_01MBVNhRX6XKTHqtZ6zS1i6w * fix: resolve code review findings for TUI prompt corruption PR - Move "workspace created" log inside success branch (was logging on failure) - Use structured metadata in `client.ts` stderr handler instead of template string - Downgrade noisy diagnostic logs to `debug` level (clipboard, theme, route, sync) - Replace platform-dependent binaries (`echo`, `python3`, `tar`) with `process.execPath` in tests for CI portability - Replace hollow Log test with real `Log.init({ print: false })` integration test - Use regex matching instead of brittle exact-string matching in log assertion tests - Add source-scanning regression tests for TUI files and `client.ts` to prevent future `console.log/error` regressions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8d7ade9 commit 49fa941

File tree

12 files changed

+447
-53
lines changed

12 files changed

+447
-53
lines changed

packages/opencode/src/altimate/bridge/client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import path from "path"
1212
import { ensureEngine, enginePythonPath } from "./engine"
1313
import type { BridgeMethod, BridgeMethods } from "./protocol"
1414
import { Telemetry } from "../telemetry"
15+
import { Log } from "../../util/log"
1516

1617
/** Resolve the Python interpreter to use for the engine sidecar.
1718
* Exported for testing — not part of the public API. */
@@ -137,7 +138,7 @@ export namespace Bridge {
137138

138139
child.stderr!.on("data", (data: Buffer) => {
139140
const msg = data.toString().trim()
140-
if (msg) console.error(`[altimate-engine] ${msg}`)
141+
if (msg) Log.Default.error("altimate-engine stderr", { message: msg })
141142
})
142143

143144
child.on("exit", (code) => {

packages/opencode/src/altimate/bridge/engine.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { existsSync } from "fs"
1616
import fs from "fs/promises"
1717
import path from "path"
1818
import { Global } from "../../global"
19-
import { UI } from "../../cli/ui"
19+
import { Log } from "../../util/log"
2020
import { Telemetry } from "@/telemetry"
2121

2222
declare const ALTIMATE_ENGINE_VERSION: string
@@ -89,7 +89,7 @@ export async function ensureUv(): Promise<void> {
8989

9090
const url = `https://github.com/astral-sh/uv/releases/latest/download/${asset}`
9191

92-
UI.println(`${UI.Style.TEXT_DIM}Downloading uv...${UI.Style.TEXT_NORMAL}`)
92+
Log.Default.info("downloading uv")
9393

9494
const dir = engineDir()
9595
await fs.mkdir(path.join(dir, "bin"), { recursive: true })
@@ -140,7 +140,7 @@ export async function ensureUv(): Promise<void> {
140140
await fs.chmod(uv, 0o755)
141141
}
142142

143-
UI.println(`${UI.Style.TEXT_SUCCESS}uv installed${UI.Style.TEXT_NORMAL}`)
143+
Log.Default.info("uv installed")
144144
}
145145

146146
/** Creates venv + installs altimate-engine. Upgrades on version mismatch.
@@ -170,7 +170,7 @@ async function ensureEngineImpl(): Promise<void> {
170170

171171
// Create venv if it doesn't exist
172172
if (!existsSync(venvDir)) {
173-
UI.println(`${UI.Style.TEXT_DIM}Creating Python environment...${UI.Style.TEXT_NORMAL}`)
173+
Log.Default.info("creating python environment")
174174
try {
175175
execFileSync(uv, ["venv", "--python", "3.12", venvDir], { stdio: "pipe" })
176176
} catch (e: any) {
@@ -187,7 +187,7 @@ async function ensureEngineImpl(): Promise<void> {
187187

188188
// Install/upgrade engine
189189
const pythonPath = enginePythonPath()
190-
UI.println(`${UI.Style.TEXT_DIM}Installing altimate-engine ${ALTIMATE_ENGINE_VERSION}...${UI.Style.TEXT_NORMAL}`)
190+
Log.Default.info("installing altimate-engine", { version: ALTIMATE_ENGINE_VERSION })
191191
try {
192192
execFileSync(uv, ["pip", "install", "--python", pythonPath, `altimate-engine==${ALTIMATE_ENGINE_VERSION}`], { stdio: "pipe" })
193193
} catch (e: any) {
@@ -224,7 +224,7 @@ async function ensureEngineImpl(): Promise<void> {
224224
duration_ms: Date.now() - startTime,
225225
})
226226

227-
UI.println(`${UI.Style.TEXT_SUCCESS}Engine ready${UI.Style.TEXT_NORMAL}`)
227+
Log.Default.info("engine ready", { version: ALTIMATE_ENGINE_VERSION })
228228
}
229229

230230
/** Returns current engine status */

packages/opencode/src/cli/cmd/tui/app.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMo
77
import { win32DisableProcessedInput, win32FlushInputBuffer, win32InstallCtrlCGuard } from "./win32"
88
import { Installation } from "@/installation"
99
import { Flag } from "@/flag/flag"
10+
import { Log } from "@/util/log"
1011
import { DialogProvider, useDialog } from "@tui/ui/dialog"
1112
import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider"
1213
import { SDKProvider, useSDK } from "@tui/context/sdk"
@@ -239,7 +240,7 @@ export function tui(input: {
239240
keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }],
240241
onCopySelection: (text) => {
241242
Clipboard.copy(text).catch((error) => {
242-
console.error(`Failed to copy console selection to clipboard: ${error}`)
243+
Log.Default.error(`Failed to copy console selection to clipboard: ${error}`)
243244
})
244245
},
245246
},
@@ -306,7 +307,7 @@ function App() {
306307
const [terminalTitleEnabled, setTerminalTitleEnabled] = createSignal(kv.get("terminal_title_enabled", true))
307308

308309
createEffect(() => {
309-
console.log(JSON.stringify(route.data))
310+
Log.Default.debug("route changed", { route: route.data })
310311
})
311312

312313
// Update terminal window title based on current route and session

packages/opencode/src/cli/cmd/tui/component/dialog-mcp.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useTheme } from "../context/theme"
77
import { Keybind } from "@/util/keybind"
88
import { TextAttributes } from "@opentui/core"
99
import { useSDK } from "@tui/context/sdk"
10+
import { Log } from "@/util/log"
1011

1112
function Status(props: { enabled: boolean; loading: boolean }) {
1213
const { theme } = useTheme()
@@ -61,10 +62,10 @@ export function DialogMcp() {
6162
if (status.data) {
6263
sync.set("mcp", status.data)
6364
} else {
64-
console.error("Failed to refresh MCP status: no data returned")
65+
Log.Default.error("Failed to refresh MCP status: no data returned")
6566
}
6667
} catch (error) {
67-
console.error("Failed to toggle MCP:", error)
68+
Log.Default.error("Failed to toggle MCP", { error })
6869
} finally {
6970
setLoading(null)
7071
}

packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { Session } from "@opencode-ai/sdk/v2"
77
import { useSDK } from "../context/sdk"
88
import { useToast } from "../ui/toast"
99
import { useKeybind } from "../context/keybind"
10+
import { Log } from "@/util/log"
1011
import { DialogSessionList } from "./workspace/dialog-session-list"
1112
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
1213

@@ -112,10 +113,9 @@ function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) => Promi
112113
setCreating(type)
113114

114115
const result = await sdk.client.experimental.workspace.create({ type, branch: null }).catch((err) => {
115-
console.log(err)
116+
Log.Default.error("workspace creation failed", { error: err })
116117
return undefined
117118
})
118-
console.log(JSON.stringify(result, null, 2))
119119
const workspace = result?.data
120120
if (!workspace) {
121121
setCreating(undefined)
@@ -125,6 +125,7 @@ function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) => Promi
125125
})
126126
return
127127
}
128+
Log.Default.info("workspace created", { workspaceId: workspace.id })
128129
await sync.workspace.sync()
129130
await props.onSelect(workspace.id)
130131
setCreating(undefined)

packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { useExit } from "../../context/exit"
2323
import { Clipboard } from "../../util/clipboard"
2424
import type { FilePart } from "@opencode-ai/sdk/v2"
2525
import { TuiEvent } from "../../event"
26+
import { Log } from "@/util/log"
2627
import { iife } from "@/util/iife"
2728
import { Locale } from "@/util/locale"
2829
import { formatDuration } from "@/util/format"
@@ -603,7 +604,7 @@ export function Prompt(props: PromptProps) {
603604
})
604605

605606
if (res.error) {
606-
console.log("Creating a session failed:", res.error)
607+
Log.Default.error("Creating a session failed", { error: res.error })
607608

608609
toast.show({
609610
message: "Creating a session failed. Open console for more details.",
@@ -640,7 +641,7 @@ export function Prompt(props: PromptProps) {
640641
}
641642
} catch (err) {
642643
// Enhancement failure should never block prompt submission
643-
console.error("auto-enhance failed, using original prompt", err)
644+
Log.Default.error("auto-enhance failed, using original prompt", { error: err })
644645
} finally {
645646
enhancingInProgress = false
646647
}

packages/opencode/src/cli/cmd/tui/context/route.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createStore } from "solid-js/store"
22
import { createSimpleContext } from "./helper"
33
import type { PromptInfo } from "../component/prompt/history"
4+
import { Log } from "@/util/log"
45

56
export type HomeRoute = {
67
type: "home"
@@ -32,7 +33,7 @@ export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
3233
return store
3334
},
3435
navigate(route: Route) {
35-
console.log("navigate", route)
36+
Log.Default.debug("navigate", { route })
3637
setStore(route)
3738
},
3839
}

packages/opencode/src/cli/cmd/tui/context/sync.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
356356
const args = useArgs()
357357

358358
async function bootstrap() {
359-
console.log("bootstrapping")
359+
Log.Default.debug("bootstrapping")
360360
const start = Date.now() - 30 * 24 * 60 * 60 * 1000
361361
const sessionListPromise = sdk.client.session
362362
.list({ start: start })

packages/opencode/src/cli/cmd/tui/context/theme.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { SyntaxStyle, RGBA, type TerminalColors } from "@opentui/core"
2+
import { Log } from "@/util/log"
23
import path from "path"
34
import { createEffect, createMemo, onMount } from "solid-js"
45
import { createSimpleContext } from "./helper"
@@ -319,13 +320,13 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
319320
onMount(init)
320321

321322
function resolveSystemTheme() {
322-
console.log("resolveSystemTheme")
323+
Log.Default.debug("resolving system theme")
323324
renderer
324325
.getPalette({
325326
size: 16,
326327
})
327328
.then((colors) => {
328-
console.log(colors.palette)
329+
Log.Default.debug("system theme palette", { palette: colors.palette })
329330
if (!colors.palette[0]) {
330331
if (store.active === "system") {
331332
setStore(

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
useContext,
1414
} from "solid-js"
1515
import { Dynamic } from "solid-js/web"
16+
import { Log } from "@/util/log"
1617
import path from "path"
1718
import { useRoute, useRouteData } from "@tui/context/route"
1819
import { useSync } from "@tui/context/sync"
@@ -195,7 +196,7 @@ export function Session() {
195196
if (scroll) scroll.scrollBy(100_000)
196197
})
197198
.catch((e) => {
198-
console.error(e)
199+
Log.Default.error("session sync failed", { error: e })
199200
toast.show({
200201
message: `Session not found: ${route.sessionID}`,
201202
variant: "error",

0 commit comments

Comments
 (0)