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 state: EventState = {
mainSessionIdle: true,
mainSessionError: false,
lastError: null,
lastOutput: "",
lastPartText: "",
currentTool: null,

View File

@@ -4,6 +4,7 @@ import type {
EventPayload,
SessionIdleProps,
SessionStatusProps,
SessionErrorProps,
MessageUpdatedProps,
MessagePartUpdatedProps,
ToolExecuteProps,
@@ -12,6 +13,8 @@ import type {
export interface EventState {
mainSessionIdle: boolean
mainSessionError: boolean
lastError: string | null
lastOutput: string
lastPartText: string
currentTool: string | null
@@ -20,6 +23,8 @@ export interface EventState {
export function createEventState(): EventState {
return {
mainSessionIdle: false,
mainSessionError: false,
lastError: null,
lastOutput: "",
lastPartText: "",
currentTool: null,
@@ -43,6 +48,7 @@ export async function processEvents(
logEventVerbose(ctx, payload)
handleSessionError(ctx, payload, state)
handleSessionIdle(ctx, payload, state)
handleSessionStatus(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(
ctx: RunContext,
payload: EventPayload,

View File

@@ -87,6 +87,16 @@ export async function run(options: RunOptions): Promise<number> {
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)
if (shouldExit) {
console.log(pc.green("\n\nAll tasks completed."))

View File

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