From 5f5b476f12f3ebe16441f229e36915ccfe117122 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 17 Feb 2026 14:34:22 +0900 Subject: [PATCH] fix: gate run event traces behind --verbose --- src/cli/cli-program.ts | 2 ++ src/cli/run/event-stream-processor.ts | 8 +++-- src/cli/run/events.test.ts | 48 ++++++++++++++++++++++++++- src/cli/run/runner.ts | 8 ++++- src/cli/run/types.ts | 2 ++ 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/cli/cli-program.ts b/src/cli/cli-program.ts index 846564fe9..d9bc4dfc5 100644 --- a/src/cli/cli-program.ts +++ b/src/cli/cli-program.ts @@ -75,6 +75,7 @@ program .option("--attach ", "Attach to existing opencode server URL") .option("--on-complete ", "Shell command to run after completion") .option("--json", "Output structured JSON result to stdout") + .option("--verbose", "Show full event stream (default: messages/tools only)") .option("--session-id ", "Resume existing session instead of creating new one") .addHelpText("after", ` Examples: @@ -114,6 +115,7 @@ Unlike 'opencode run', this command waits until: attach: options.attach, onComplete: options.onComplete, json: options.json ?? false, + verbose: options.verbose ?? false, sessionId: options.sessionId, } const exitCode = await run(runOptions) diff --git a/src/cli/run/event-stream-processor.ts b/src/cli/run/event-stream-processor.ts index 4bc5bbc4f..04ba252bd 100644 --- a/src/cli/run/event-stream-processor.ts +++ b/src/cli/run/event-stream-processor.ts @@ -24,11 +24,15 @@ export async function processEvents( try { const payload = event as EventPayload if (!payload?.type) { - console.error(pc.dim(`[event] no type: ${JSON.stringify(event)}`)) + if (ctx.verbose) { + console.error(pc.dim(`[event] no type: ${JSON.stringify(event)}`)) + } continue } - logEventVerbose(ctx, payload) + if (ctx.verbose) { + logEventVerbose(ctx, payload) + } handleSessionError(ctx, payload, state) handleSessionIdle(ctx, payload, state) diff --git a/src/cli/run/events.test.ts b/src/cli/run/events.test.ts index 14010e05e..d777ba54c 100644 --- a/src/cli/run/events.test.ts +++ b/src/cli/run/events.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "bun:test" +import { describe, it, expect, spyOn } from "bun:test" import { createEventState, serializeError, type EventState } from "./events" import type { RunContext, EventPayload } from "./types" @@ -87,6 +87,52 @@ describe("createEventState", () => { }) describe("event handling", () => { + it("does not log verbose event traces by default", async () => { + // given + const ctx = createMockContext("my-session") + const state = createEventState() + const errorSpy = spyOn(console, "error").mockImplementation(() => {}) + + const payload: EventPayload = { + type: "custom.event", + properties: { sessionID: "my-session" }, + } + + const events = toAsyncIterable([payload]) + const { processEvents } = await import("./events") + + // when + await processEvents(ctx, events, state) + + // then + expect(errorSpy).not.toHaveBeenCalled() + errorSpy.mockRestore() + }) + + it("logs full event traces when verbose is enabled", async () => { + // given + const ctx = { ...createMockContext("my-session"), verbose: true } + const state = createEventState() + const errorSpy = spyOn(console, "error").mockImplementation(() => {}) + + const payload: EventPayload = { + type: "custom.event", + properties: { sessionID: "my-session" }, + } + + const events = toAsyncIterable([payload]) + const { processEvents } = await import("./events") + + // when + await processEvents(ctx, events, state) + + // then + expect(errorSpy).toHaveBeenCalledTimes(1) + const firstCall = errorSpy.mock.calls[0] + expect(String(firstCall?.[0] ?? "")).toContain("custom.event") + errorSpy.mockRestore() + }) + it("session.idle sets mainSessionIdle to true for matching session", async () => { // given const ctx = createMockContext("my-session") diff --git a/src/cli/run/runner.ts b/src/cli/run/runner.ts index a958932c0..e56accc81 100644 --- a/src/cli/run/runner.ts +++ b/src/cli/run/runner.ts @@ -84,7 +84,13 @@ export async function run(options: RunOptions): Promise { console.log(pc.dim(`Session: ${sessionID}`)) - const ctx: RunContext = { client, sessionID, directory, abortController } + const ctx: RunContext = { + client, + sessionID, + directory, + abortController, + verbose: options.verbose ?? false, + } const events = await client.event.subscribe({ query: { directory } }) const eventState = createEventState() const eventProcessor = processEvents(ctx, events.stream, eventState).catch( diff --git a/src/cli/run/types.ts b/src/cli/run/types.ts index ff099de27..f310a2a3e 100644 --- a/src/cli/run/types.ts +++ b/src/cli/run/types.ts @@ -4,6 +4,7 @@ export type { OpencodeClient } export interface RunOptions { message: string agent?: string + verbose?: boolean directory?: string timeout?: number port?: number @@ -31,6 +32,7 @@ export interface RunContext { sessionID: string directory: string abortController: AbortController + verbose?: boolean } export interface Todo {