fix(cli): handle session.error in run command to prevent infinite wait

When session.error occurs with incomplete todos, the run command now:
- Captures the error via handleSessionError()
- Exits with code 1 instead of waiting indefinitely
- Shows clear error message to user

Previously, run command ignored session.error events, causing infinite
'Waiting: N todos remaining' loop when agent errors occurred.

🤖 Generated with assistance of OhMyOpenCode
https://github.com/code-yeongyu/oh-my-opencode
This commit is contained in:
YeonGyu-Kim
2025-12-25 22:34:41 +09:00
parent 9bfe7d8a1d
commit 0b4821cfdf
4 changed files with 40 additions and 0 deletions

View File

@@ -74,6 +74,8 @@ describe("event handling", () => {
const ctx = createMockContext("my-session") const ctx = createMockContext("my-session")
const state: EventState = { const state: EventState = {
mainSessionIdle: true, mainSessionIdle: true,
mainSessionError: false,
lastError: null,
lastOutput: "", lastOutput: "",
lastPartText: "", lastPartText: "",
currentTool: null, currentTool: null,

View File

@@ -4,6 +4,7 @@ import type {
EventPayload, EventPayload,
SessionIdleProps, SessionIdleProps,
SessionStatusProps, SessionStatusProps,
SessionErrorProps,
MessageUpdatedProps, MessageUpdatedProps,
MessagePartUpdatedProps, MessagePartUpdatedProps,
ToolExecuteProps, ToolExecuteProps,
@@ -12,6 +13,8 @@ import type {
export interface EventState { export interface EventState {
mainSessionIdle: boolean mainSessionIdle: boolean
mainSessionError: boolean
lastError: string | null
lastOutput: string lastOutput: string
lastPartText: string lastPartText: string
currentTool: string | null currentTool: string | null
@@ -20,6 +23,8 @@ export interface EventState {
export function createEventState(): EventState { export function createEventState(): EventState {
return { return {
mainSessionIdle: false, mainSessionIdle: false,
mainSessionError: false,
lastError: null,
lastOutput: "", lastOutput: "",
lastPartText: "", lastPartText: "",
currentTool: null, currentTool: null,
@@ -43,6 +48,7 @@ export async function processEvents(
logEventVerbose(ctx, payload) logEventVerbose(ctx, payload)
handleSessionError(ctx, payload, state)
handleSessionIdle(ctx, payload, state) handleSessionIdle(ctx, payload, state)
handleSessionStatus(ctx, payload, state) handleSessionStatus(ctx, payload, state)
handleMessagePartUpdated(ctx, payload, state) handleMessagePartUpdated(ctx, payload, state)
@@ -154,6 +160,23 @@ function handleSessionStatus(
} }
} }
function handleSessionError(
ctx: RunContext,
payload: EventPayload,
state: EventState
): void {
if (payload.type !== "session.error") return
const props = payload.properties as SessionErrorProps | undefined
if (props?.sessionID === ctx.sessionID) {
state.mainSessionError = true
state.lastError = props?.error
? String(props.error instanceof Error ? props.error.message : props.error)
: "Unknown error"
console.error(pc.red(`\n[session.error] ${state.lastError}`))
}
}
function handleMessagePartUpdated( function handleMessagePartUpdated(
ctx: RunContext, ctx: RunContext,
payload: EventPayload, payload: EventPayload,

View File

@@ -87,6 +87,16 @@ export async function run(options: RunOptions): Promise<number> {
continue continue
} }
// Check if session errored - exit with failure if so
if (eventState.mainSessionError) {
console.error(pc.red(`\n\nSession ended with error: ${eventState.lastError}`))
console.error(pc.yellow("Check if todos were completed before the error."))
abortController.abort()
await eventProcessor.catch(() => {})
cleanup()
return 1
}
const shouldExit = await checkCompletionConditions(ctx) const shouldExit = await checkCompletionConditions(ctx)
if (shouldExit) { if (shouldExit) {
console.log(pc.green("\n\nAll tasks completed.")) console.log(pc.green("\n\nAll tasks completed."))

View File

@@ -69,3 +69,8 @@ export interface ToolResultProps {
name?: string name?: string
output?: string output?: string
} }
export interface SessionErrorProps {
sessionID?: string
error?: unknown
}