Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/opencode/src/session/compaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,13 +402,20 @@ const layer = Layer.effect(
})

if (result === "compact") {
// Recovery abandoned: this is where overflow becomes an outcome, so
// the error event fires here rather than on the recoverable 413 in
// processor.halt.
processor.message.error = new SessionV1.ContextOverflowError({
message: replay
? "Conversation history too large to compact - exceeds model context limit"
: "Session too large to compact - context exceeds model limit even after stripping media",
}).toObject()
processor.message.finish = "error"
yield* session.updateMessage(processor.message)
yield* events.publish(Session.Event.Error, {
sessionID: input.sessionID,
error: processor.message.error,
})
return "stop"
}

Expand Down
7 changes: 6 additions & 1 deletion packages/opencode/src/session/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,8 +638,13 @@ const layer = Layer.effect(
yield* status.set(ctx.sessionID, { type: "idle" })
return
}
// Recovery attempt, not an outcome: auto-compaction will retry, so
// nothing is published here. The durable message carries no error
// either; announcing one on the error channel made every consumer
// (CLI exit code, TUI toast, orchestrators) fail runs that recover.
// If compaction cannot shrink the session, compaction.ts publishes
// the terminal error.
ctx.needsCompaction = true
yield* events.publish(Session.Event.Error, { sessionID: ctx.sessionID, error })
return
}
ctx.assistantMessage.error = error
Expand Down
57 changes: 57 additions & 0 deletions packages/opencode/test/cli/run/run-process.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,63 @@ describe("opencode run (non-interactive subprocess)", () => {
60_000,
)

cliIt.concurrent(
"recovers from a provider size error via compaction without emitting an error event",
({ llm, opencode }) =>
Effect.gen(function* () {
yield* llm.error(413, {
error: { type: "request_too_large", message: "Request exceeds the maximum size" },
})
yield* llm.text("compacted history")
yield* llm.text("recovered output")

const result = yield* opencode.run("recover after overflow", {
format: "json",
env: { OPENCODE_DISABLE_AUTOCOMPACT: "0" },
})

opencode.expectExit(result, 0)
const events = opencode.parseJsonEvents(result.stdout)
expect(events.some((event) => event.type === "error")).toBe(false)
expect(
events.some(
(event) =>
event.type === "text" &&
typeof event.part === "object" &&
event.part !== null &&
"text" in event.part &&
event.part.text === "recovered output",
),
).toBe(true)
}),
60_000,
)

cliIt.concurrent(
"exits nonzero with an error event when compaction cannot shrink the session",
({ llm, opencode }) =>
Effect.gen(function* () {
yield* llm.error(413, {
error: { type: "request_too_large", message: "Request exceeds the maximum size" },
})
yield* llm.error(413, {
error: { type: "request_too_large", message: "Request exceeds the maximum size" },
})

const result = yield* opencode.run("overflow beyond recovery", {
format: "json",
env: { OPENCODE_DISABLE_AUTOCOMPACT: "0" },
})

opencode.expectExit(result, 1)
const events = opencode.parseJsonEvents(result.stdout)
const errors = events.filter((event) => event.type === "error")
expect(errors.length).toBe(1)
expect(JSON.stringify(errors[0])).toContain("too large to compact")
}),
60_000,
)

cliIt.concurrent(
"rejects requested permissions by default and allows them with the dangerous flag",
({ home, llm, opencode }) =>
Expand Down
Loading