Dashboard › opencode › Session 0IkUYcVS7JSd
0IkUYcVS7JSdLPA26Perform a complete independent read-only correctness/adversarial review of the frozen interrupted-session recovery candidate. Do not edit files, run builds/tests, start services, or mutate VCS. The required repo-setup skill is unavailable; explicitly note that and read applicable AGENTS.md/package instructions directly. Review ONLY immutable base ab9408c81cc9916177641ae8e9f08624fc27b5b9 plus patch /tmp/opencode/session-recovery-final-v4.patch. First verify patch SHA-256 c2c8afb07a7b6a49c1d40c5515d718ad5a934c9291e80a068693259c26c566a0, SEA artifact /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server SHA-256 56c1bc48b6e21397db05eb0d41f73d243a4b64da093fcfbe209baed65457f6e8, and that the patch applies cleanly to the exact base. Inspect all 33 changed files from immutable Git/base+patch, not the mutable worktree. Reassess all lifecycle, replay, migration, concurrency, liveness, and exactly-once-safety boundaries. Specifically verify the four latest fixes: (1) provider-complete hosted tool calls left pending/running are reconciled before Step.Ended can commit phase continue; (2) recovery revalidates latest-assistant identity before wake, including no-assistant synthetic recovery superseded before scanner validation; (3) bounded recovery and ordinary wake scans cannot starve later Sessions when the first 100 wakes are no-ops or fail, including across process restarts, while each pass remains bounded; (4) SessionRunCoordinator finalizes active state and successor wakes when interruption cleanup fails/dies, while propagating the cleanup failure. Also review claim fencing, cancellation, compaction, pre-promotion claim crashes, recovery-owned input reservation across public retry/replay/queue/steer/redrive, durable prompt wake intent and historical v1 compatibility, wake-attempt schema/migration/indexes, poison quarantine, cross-Session identities, generated OpenAPI/SDK, Core/Server dependency direction, and tests for fail-first relevance. ...
The required repo-setup skill is unavailable in this environment. I will read the applicable instructions and package metadata from immutable base ab9408c81cc9916177641ae8e9f08624fc27b5b9, verify the supplied hashes and patch applicability without changing the worktree or repository, then review every patched file from base-plus-patch evidence only.
c2c8afb07a7b6a49c1d40c5515d718ad5a934c9291e80a068693259c26c566a0 /tmp/opencode/session-recovery-final-v4.patch 56c1bc48b6e21397db05eb0d41f73d243a4b64da093fcfbe209baed65457f6e8 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server
commit ab9408c81cc9916177641ae8e9f08624fc27b5b9 d3323b963b9ef2de41a7ff9884299c74b93886ba 2e9407768d1401a25b69febb36657638a779053a feat(app): adapt vertical tab density - To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`. - After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly. - Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server. - The default branch in this repo is `dev`. - Local `main` ref may not exist; use `dev` or `origin/dev` for diffs. ## Branch Names Use a short branch name of at most three words, separated by hyphens. Do not use slashes or type prefixes such as `feat/` or `fix/`. Examples: `session-recovery`, `fix-scroll-state`, `regenerate-sdk`. ...
AGENTS.md CONTRIBUTING.md artifacts/glm52-rise-video/package.json github/package.json package.json packages/app/AGENTS.md packages/app/e2e/AGENTS.md packages/app/e2e/performance/AGENTS.md packages/app/package.json packages/cli/package.json packages/client/package.json packages/codemode/AGENTS.md packages/codemode/package.json packages/console/app/package.json packages/console/core/package.json packages/console/function/package.json packages/console/mail/package.json packages/console/resource/package.json packages/console/support/package.json packages/core/package.json packages/core/src/tool/AGENTS.md packages/desktop/AGENTS.md packages/desktop/package.json packages/effect-drizzle-sqlite/AGENTS.md packages/effect-drizzle-sqlite/package.json packages/effect-sqlite-node/package.json packages/enterprise/package.json packages/function/package.json packages/http-recorder/package.json packages/httpapi-codegen/package.json packages/llm/AGENTS.md packages/llm/package.json packages/opencode/AGEN...
527 48 packages/core/schema.json 1 0 packages/core/src/database/migration.gen.ts 55 0 packages/core/src/database/migration/20260914170650_session-recovery.ts 45 0 packages/core/src/database/schema.gen.ts 5 4 packages/core/src/session.ts 31 16 packages/core/src/session/compaction.ts 54 34 packages/core/src/session/context-epoch.ts 66 0 packages/core/src/session/execution/claim.ts 118 19 packages/core/src/session/execution/local.ts 342 31 packages/core/src/session/input.ts 69 0 packages/core/src/session/message-updater.ts 77 1 packages/core/src/session/projector.ts 6 0 packages/core/src/session/recovery-id.ts 315 0 packages/core/src/session/recovery.ts 30 5 packages/core/src/session/run-coordinator.ts 4 0 packages/core/src/session/runner/index.ts 207 90 packages/core/src/session/runner/llm.ts 74 28 packages/core/src/session/runner/publish-llm-event.ts 70 1 packages/core/src/session/sql.ts 63 1 packages/core/test/database-migration.test.ts 239 0 packages/core/test/session-execution-local....
patch applies cleanly to base via isolated index
# Contributing to OpenCode We want to make it easy for you to contribute to OpenCode. Here are the most common type of changes that get merged: - Bug fixes - Additional LSPs / Formatters - Improvements to LLM performance - Support for new providers - Fixes for environment-specific quirks - Missing standard behavior - Documentation improvements However, any UI or core product feature must go through a design review with the core team before implementation. If you are unsure if a PR would be accepted, feel free to ask a maintainer or look for issues with any of the following labels: - [`help wanted`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Ahelp-wanted) - [`good first issue`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) - [`bug`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) - ...
The supplied patch and SEA hashes match exactly. An isolated temporary index built from the named commit confirms the patch applies cleanly without touching the repository index, worktree, refs, or objects; the patch contains exactly 33 changed files. I have also read the base commit’s root, Core/OpenCode, and Schema instructions plus the affected package scripts, and will now trace the candidate state machine and its tests across those 33 files.
Found 33 matches /tmp/opencode/session-recovery-final-v4.patch: Line 1: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 870: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 881: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 942: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 1033: diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts Line 1065: diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts Line 1154: diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts Line 1330: diff --git a/packages/core/src/session/execution/claim.ts b/packages/core/src/session/execution/claim.ts Line 1402: diff --git ...
<path>/tmp/opencode/session-recovery-final-v4.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/core/schema.json b/packages/core/schema.json
2: index d0eeeebd5c..9ea51c4b59 100644
3: --- a/packages/core/schema.json
4: +++ b/packages/core/schema.json
5: @@ -1,8 +1,10 @@
6: {
7: "version": "7",
8: "dialect": "sqlite",
9: - "id": "f14a9b18-8207-487e-a3d3-227e629ba9ad",
10: - "prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
11: + "id": "539b8b9f-47d0-4d6d-b29f-4282139c71eb",
12: + "prevIds": [
13: + "797e6838-8a41-4793-932a-d200aa8d51fb"
14: + ],
15: "ddl": [
16: {
17: "name": "workspace",
18: @@ -60,6 +62,10 @@
19: "name": "session_context_epoch",
20: "entityType": "tables"
21: },
22: + {
23: + "name": "session_execution",
24: + "entityType": "tables"
25: + },
26: {
27: "name": "session_input",
28: "entityType": "tables"
29: @@ -68,6 +74,10 @@
30: "name": "session_message",
31: ...
<path>/tmp/opencode/session-recovery-final-v4.patch</path>
<type>file</type>
<content>
1001: @@ -246,6 +280,8 @@ export default {
1002: )
1003: yield* tx.run(`CREATE INDEX \`part_message_id_id_idx\` ON \`part\` (\`message_id\`,\`id\`);`)
1004: yield* tx.run(`CREATE INDEX \`part_session_idx\` ON \`part\` (\`session_id\`);`)
1005: + yield* tx.run(`CREATE UNIQUE INDEX \`session_execution_session_idx\` ON \`session_execution\` (\`session_id\`);`)
1006: + yield* tx.run(`CREATE INDEX \`session_execution_owner_idx\` ON \`session_execution\` (\`owner_id\`);`)
1007: yield* tx.run(
1008: `CREATE INDEX \`session_input_session_pending_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`delivery\`,\`admitted_seq\`);`,
1009: )
1010: @@ -255,6 +291,9 @@ export default {
1011: yield* tx.run(
1012: `CREATE UNIQUE INDEX \`session_input_session_promoted_seq_idx\` ON \`session_input\` ...
<path>/tmp/opencode/session-recovery-final-v4.patch</path>
<type>file</type>
<content>
2001: + sessionID,
2002: + timestamp: DateTime.makeUnsafe(row.time_created),
2003: + messageID: id,
2004: + prompt: decodePrompt(row.prompt),
2005: + delivery: row.delivery,
2006: + },
2007: + commit ? { commit: () => commit(row) } : undefined,
2008: + )
2009: .pipe(
2010: Effect.catchDefect((defect) =>
2011: defect instanceof LifecycleConflict
2012: @@ -247,42 +500,100 @@ export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
2013: events: EventV2.Interface,
2014: sessionID: SessionSchema.ID,
2015: cutoff: number,
2016: + commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
2017: ) {
2018: const rows = yield* db
2019: - .select()
2020: + .select({ input: SessionInputTable })
2021: .from(SessionInputTable)
2022: + .leftJoin(
2023: + ...
<path>/tmp/opencode/session-recovery-final-v4.patch</path>
<type>file</type>
<content>
3001: + Effect.gen(function* () {
3002: + yield* FiberSet.clear(toolFibers)
3003: + yield* withPublication(publisher.failUnsettledTools("Tool execution outcome unknown after interruption"))
3004: + yield* withPublication(publisher.interruptAssistant("Provider Step outcome unknown after interruption"))
3005: + return yield* Effect.failCause(cause)
3006: + })
3007: + if (stream._tag === "Failure" && Cause.hasInterrupts(stream.cause))
3008: + return yield* settleInterruption(stream.cause)
3009: + if (settled._tag === "Failure" && Cause.hasInterrupts(settled.cause))
3010: + return yield* settleInterruption(settled.cause)
3011: if (settled._tag === "Failure" && !Cause.hasInterrupts(settled.cause)) {
3012: const failure = Cause.squash(settled.cause)
3013: ...
<path>/tmp/opencode/session-recovery-final-v4.patch</path>
<type>file</type>
<content>
4001: + .all()
4002: + yield* events.remove(sessionID)
4003: + yield* db.delete(SessionRecoveryTable).run()
4004: + yield* db.delete(SessionInputTable).where(eq(SessionInputTable.session_id, sessionID)).run()
4005: + yield* db.delete(SessionMessageTable).where(eq(SessionMessageTable.session_id, sessionID)).run()
4006: + yield* events.replayAll(
4007: + recorded.map((event) => ({
4008: + id: event.id,
4009: + aggregateID: event.aggregate_id,
4010: + seq: event.seq,
4011: + type: event.type,
4012: + data: event.data,
4013: + })),
4014: + )
4015: + expect(yield* db.select().from(SessionRecoveryTable).all()).toHaveLength(1)
4016: + expect(yield* db.select().from(SessionInputTable).all()).toHaveLength(1)
4017: + expect(yield* db.select().from(SessionMessageTable).all()).toHaveLength(1)
4018: ...
<path>/tmp/opencode/session-recovery-final-v4.patch</path>
<type>file</type>
<content>
5001: + yield* db
5002: + .update(SessionInputTable)
5003: + .set({ delivery: "steer" })
5004: + .where(eq(SessionInputTable.id, continuationID))
5005: + .run()
5006: + yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, recoveryExecution))
5007: + expect(yield* db.select().from(SessionRecoveryTable).get()).toMatchObject({
5008: + recovery_error: "Invalid recovery continuation input",
5009: + })
5010: + expect(yield* SessionInput.hasPending(db, sessionID, "steer")).toBe(false)
5011: + expect(yield* SessionInput.promoteSteers(db, events, sessionID, Number.MAX_SAFE_INTEGER)).toBe(0)
5012: +
5013: + const ordinaryID = SessionMessage.ID.make("msg_recovery_later_steer")
5014: + yield* SessionInput.admit(db, events, {
5015: + id: ordinaryID,
5016: + sessionID,
5017: + prompt: ...
<path>/tmp/opencode/session-recovery-final-v4.patch</path>
<type>file</type>
<content>
6001: + expect(
6002: + yield* db
6003: + .select({ id: SessionExecutionTable.id })
6004: + .from(SessionExecutionTable)
6005: + .where(eq(SessionExecutionTable.session_id, sessionID))
6006: + .get(),
6007: + ).toBeUndefined()
6008: + expect(
6009: + (yield* db.select({ type: EventTable.type }).from(EventTable).all()).some((event) =>
6010: + event.type.startsWith(SessionEvent.Step.Interrupted.type),
6011: + ),
6012: + ).toBe(true)
6013: expect(yield* session.context(sessionID)).toMatchObject([
6014: { type: "user", text: prompt },
6015: {
6016: type: "assistant",
6017: finish: "error",
6018: - error: { type: "unknown", message: "Provider turn interrupted" },
6019: + error: { type: "unknown", message: "Provider Step outcome unknown after interruption" },
6020: content: [
6021: ...
<path>/tmp/opencode/session-recovery-final-v4.patch</path>
<type>file</type>
<content>
7001: + durable?: {
7002: + aggregateID: string
7003: + seq: number
7004: + version: number
7005: + }
7006: + location?: LocationRef
7007: + data: {
7008: + timestamp: number
7009: + sessionID: string
7010: + assistantMessageID: string
7011: + error: SessionErrorUnknown
7012: + }
7013: +}
7014: +
7015: +export type SessionNextStepRecovered = {
7016: + id: string
7017: + metadata?: {
7018: + [key: string]: unknown
7019: + }
7020: + type: "session.next.step.recovered"
7021: + durable?: {
7022: + aggregateID: string
7023: + seq: number
7024: + version: number
7025: + }
7026: + location?: LocationRef
7027: + data: {
7028: + timestamp: number
7029: + sessionID: string
7030: + executionID: string
7031: + assistantMessageID?: string
7032: + phase: "ready" | "unknown" | "continue"
7033: + }
7034: +}
7035: +
7036: export type ...
The four newest fixes are present in the intended boundaries: hosted-tool reconciliation now precedes Step.Ended; recovery identity is checked both by the scanner and exact promotion; persisted attempt counters rotate failed/no-op wakes behind untouched Sessions; and coordinator interruption records the owner exit before cleanup, then settles or starts a successor even when cleanup dies. I am checking whether adjacent transaction, replay, and failure paths can bypass those guards, especially through EventV2 commit ordering and claim finalization.
export * as SessionRunCoordinator from "./run-coordinator"
import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
/** Serializes execution for each key while allowing different keys to run concurrently. */
export interface Coordinator<Key, E> {
/** Snapshots keys with an execution owned by this coordinator. */
readonly active: Effect.Effect<ReadonlySet<Key>>
/** Starts execution while idle or joins the active execution. */
readonly run: (key: Key) => Effect.Effect<void, E>
/** Registers one coalesced follow-up after newly recorded work. */
readonly wake: (key: Key) => Effect.Effect<void>
/** Stops active execution and waits for its cleanup. */
readonly interrupt: (key: Key) => Effect.Effect<void>
}
type Entry<E> = {
readonly done: Deferred.Deferred<void, E>
owner?: Fiber.Fiber<void, never>
pendingWake: boolean
stopping: boolean
}
export const make = <Key, E>(options: {
readonly drain: (key: Key, force: boolean) => Effect.Effect<void, ...
1 import { describe, expect } from "bun:test"
2 import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect"
3 import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
4 import { testEffect } from "./lib/effect"
5
6 const it = testEffect(Layer.empty)
7
8 describe("SessionRunCoordinator", () => {
9 it.effect("joins concurrent resumes for one key", () =>
10 Effect.scoped(
11 Effect.gen(function* () {
12 const gate = yield* Deferred.make<void>()
13 let runs = 0
14 const coordinator = yield* SessionRunCoordinator.make({
15 drain: () => Effect.sync(() => runs++).pipe(Effect.andThen(Deferred.await(gate))),
16 })
17
18 const first = yield* coordinator.run("session").pipe(Effect.forkChild)
19 yield* Effect.yieldNow
20 const second = yield* coordinator.run("session").pipe(Effect.forkChild)
...
Found 5 matches
/tmp/opencode/session-recovery-final-v4.patch:
Line 2285: + return yield* Effect.die(`Recovery target is not the latest assistant: ${event.data.assistantMessageID}`)
Line 4189: + it.effect("never recovers an execution superseded by a newer assistant", () =>
Line 4796: + it.effect("quarantines a recovery wake behind a newer assistant", () =>
Line 4911: + it.effect("never promotes a recovery superseded before scanner validation", () =>
Line 6027: + it.effect("redrives only the synthetic recovery continuation", () =>
1 export * as SessionInput from "./input"
2
3 import { and, asc, eq, isNull, lte } from "drizzle-orm"
4 import { DateTime, Effect, Schema } from "effect"
5 import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
6 import type { Database } from "../database/database"
7 import type { EventV2 } from "../event"
8 import { SessionEvent } from "./event"
9 import { SessionMessage } from "./message"
10 import { Prompt } from "./prompt"
11 import { SessionSchema } from "./schema"
12 import { SessionInputTable, SessionMessageTable } from "./sql"
13
14 type DatabaseService = Database.Interface["db"]
15
16 export { Admitted, Delivery }
17
18 const decodePrompt = Schema.decodeUnknownSync(Prompt)
19 const encodePrompt = Schema.encodeSync(Prompt)
20
21 const fromRow = (row: typeof SessionInputTable.$inferSelect): Admitted =>
22 Admitted.make({
23 admittedSeq: ...
1 import {
2 LLM,
3 LLMClient,
4 LLMError,
5 LLMEvent,
6 Message,
7 SystemPart,
8 isContextOverflowFailure,
9 type ProviderErrorEvent,
10 } from "@opencode-ai/llm"
11 import { Cause, DateTime, Effect, FiberSet, Layer, Option, Semaphore, Stream } from "effect"
12 import { AgentV2 } from "../../agent"
13 import { Config } from "../../config"
14 import { Database } from "../../database/database"
15 import { EventV2 } from "../../event"
16 import { Location } from "../../location"
17 import { ModelV2 } from "../../model"
18 import { PermissionV2 } from "../../permission"
19 import { ProviderV2 } from "../../provider"
20 import { QuestionV2 } from "../../question"
21 import { SystemContext } from "../../system-context/index"
22 import { SystemContextRegistry } from "../../system-context/registry"
23 import { SkillGuidance } from "../../skill/guidance"
24 import { ...
"id": "f14a9b18-8207-487e-a3d3-227e629ba9ad",
"prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
"name": "id",
"name": "id",
"name": "id",
"name": "id",
"name": "id",
"name": "id",
"name": "id",
"name": "id",
"name": "id",
"name": "id",
"name": "id",
"name": "id",
"name": "id",
"columnsTo": ["id"],
"columnsTo": ["id"],
"columnsTo": ["id"],
"columnsTo": ["id"],
"columnsTo": ["id"],
"columnsTo": ["id"],
"columnsTo": ["id"],
"columnsTo": ["id"],
"columnsTo": ["id"],
"columnsTo": ["id"],
"columnsTo": ["id"],
"columnsTo": ["id"],
"columns": ["id"],
"columns": ["id"],
"columns": ["id"],
"columns": ["id"],
"columns": ["id"],
"columns": ["id"],
"columns": ["id"],
"columns": ["id"],
"columns": ["id"],
"columns": ["id"],
"columns": ["id"],
"columns": ["id"],
...
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session/context-epoch.ts:28: return initializeOnce(db, context, sessionID).pipe(Effect.withSpan("SessionContextEpoch.initialize"))
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session/context-epoch.ts:37: return prepareOnce(db, events, context, sessionID).pipe(Effect.withSpan("SessionContextEpoch.prepare"))
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session/runner/llm.ts:183: const initialized = yield* SessionContextEpoch.initialize(db, loadSystemContext(agent), session.id)
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session/runner/llm.ts:198: initialized ?? (yield* SessionContextEpoch.prepare(db, events, loadSystemContext(agent), session.id))
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session.ts:368: const admitted = yield* SessionInput.admit(db, events, {
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session/input.ts:41:export ...
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/app/src/context/server-session-v2-reducer.ts:330: case "session.execution.interrupted": {
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/app/src/context/server-session.ts:982: event.type === "session.execution.interrupted"
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session.ts:191: const execution = yield* SessionExecution.Service
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session.ts:431: Effect.uninterruptible(execution.interrupt(sessionID)),
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session/execution/local.ts:12: SessionExecution.Service,
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session/execution/local.ts:31: return SessionExecution.Service.of({
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session/execution/local.ts:41: service: ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0a9ba8a7d0011h91lKVfk0Zcjl
packages/core/test/session-runner.test.ts:50:+ SessionRecoveryTable,
packages/core/test/session-runner.test.ts:62:+ import { asc, eq, sql } from "drizzle-orm"
packages/core/test/session-runner.test.ts:66:+ const executionID = "test-execution"
packages/core/test/session-runner.test.ts:247:+ return yield* SessionExecutionLocal.make({
packages/core/test/session-runner.test.ts:248:+ run: (claim, force, recoveryInputID) => sessionRunner.run({ ...claim, force, recoveryInputID }),
packages/core/test/session-runner.test.ts:251:+ ).pipe(Layer.provide([runnerLayer, LayerNode.compile(Database.node)]))
packages/core/test/session-runner.test.ts:310:+ const insertExecution = (id: SessionV2.ID) =>
packages/core/test/session-runner.test.ts:311:+ Effect.gen(function* () {
packages/core/test/session-runner.test.ts:312:+ const { db } = yield* ...
Found 36 matches /home/byk/.local/share/opencode/tool-output/tool_0a9ba8a7d0011h91lKVfk0Zcjl: Line 30: packages/core/src/session/execution/local.ts:39:+ expires_at: now + leaseDuration, Line 53: packages/core/src/session/execution/local.ts:62:+ return yield* Effect.raceFirst( Line 64: packages/core/src/session/execution/local.ts:73:+ Effect.onExit((exit) => Line 68: packages/core/src/session/execution/local.ts:77:+ yield* SessionExecutionClaim.clear(db, claim) Line 85: packages/core/src/session/execution/local.ts:94:+ yield* SessionExecutionClaim.clear(db, claim) Line 94: packages/core/src/session/execution/local.ts:103:+ interrupt: (sessionID) => Line 100: packages/core/src/session/execution/local.ts:109:+ ? SessionExecutionClaim.clear(db, claim).pipe( Line 188: packages/core/src/session/recovery.ts:70:+ .where(and(lte(SessionExecutionTable.expires_at, scanTime), ...
packages/core/src/session/input.ts:255: const validation = yield* validateRecovery(db, input.id)
packages/core/src/session/input.ts:321: export const validateRecovery = Effect.fn("SessionInput.validateRecovery")(function* (
packages/core/src/session/input.ts:347: const latestAssistant = yield* db
packages/core/src/session/input.ts:373: recovery.recovery_error ??
packages/core/src/session/input.ts:391: (recovery.assistant_message_id !== null && latestAssistant?.id !== recovery.assistant_message_id) ||
packages/core/src/session/input.ts:392: (latestAssistant !== undefined && latestAssistant.seq > input.admitted_seq) ||
packages/core/src/session/input.ts:394: ? "Invalid recovery projection identity"
packages/core/src/session/input.ts:561: export const promoteExact = Effect.fn("SessionInput.promoteExact")(function* (
packages/core/src/session/input.ts:587: .set({ recovery_error: defect.reason ...
Found 56 matches
/home/byk/.local/share/opencode/tool-output/tool_0a9ba8a7d0011h91lKVfk0Zcjl:
Line 1152: packages/core/test/session-projector.test.ts:288:+ yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, recoveryExecution))
Line 1153: packages/core/test/session-projector.test.ts:289:+ yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, recoveryExecution))
Line 1164: packages/core/test/session-projector.test.ts:300:+ expect(messages[0]?.type).toBe("synthetic")
Line 1198: packages/core/test/session-projector.test.ts:334:+ yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, recoveryExecution))
Line 1206: packages/core/test/session-projector.test.ts:342:+ it.effect("never recovers an execution superseded by a newer assistant", () =>
Line 1245: packages/core/test/session-projector.test.ts:381:+ yield* ...
Found 12 matches
/home/byk/.local/share/opencode/tool-output/tool_0a9ba8a7d0011h91lKVfk0Zcjl:
Line 733: packages/core/test/session-execution-local.test.ts:63:+ yield* Scope.close(child, Exit.void)
Line 771: packages/core/test/session-execution-local.test.ts:101:+ yield* Scope.close(firstScope, Exit.void)
Line 798: packages/core/test/session-execution-local.test.ts:128:+ yield* Scope.close(firstScope, Exit.void)
Line 839: packages/core/test/session-execution-local.test.ts:169:+ yield* TestClock.adjust("10 seconds")
Line 842: packages/core/test/session-execution-local.test.ts:172:+ yield* TestClock.adjust("10 seconds")
Line 863: packages/core/test/session-execution-local.test.ts:193:+ yield* TestClock.adjust("10 seconds")
Line 870: packages/core/test/session-execution-local.test.ts:200:+ it.effect("settles cancellation before starting a pending successor", () =>
Line 874: ...
Found 100 matches (more matches available) /home/byk/.local/share/opencode/tool-output/tool_0a9ba8a7d0011h91lKVfk0Zcjl: Line 51: packages/core/src/session/execution/local.ts:60:+ .get() Line 52: packages/core/src/session/execution/local.ts:61:+ .pipe(Effect.orDie) Line 53: packages/core/src/session/execution/local.ts:62:+ return yield* Effect.raceFirst( Line 54: packages/core/src/session/execution/local.ts:63:+ input.run(claim, force, recovery?.continuationMessageID), Line 55: packages/core/src/session/execution/local.ts:64:+ SessionExecutionClaim.update(db, claim).pipe( Line 56: packages/core/src/session/execution/local.ts:65:+ Effect.repeat(Schedule.spaced(Duration.millis(leaseDuration / 3))), Line 57: packages/core/src/session/execution/local.ts:66:+ ), Line 58: packages/core/src/session/execution/local.ts:67:+ ).pipe( Line 59: packages/core/src/session/execution/local.ts:68:+ ...
MUST-FIX
Cancellation can expire into recovery while interruption cleanup is still running. SessionExecutionLocal races the runner against the heartbeat, so interrupting the owner stops both (packages/core/src/session/execution/local.ts:62-67). Interrupted drains preserve unknown and continue claims (packages/core/src/session/execution/local.ts:73-96). The coordinator waits for Fiber.interrupt(owner) before running claim cleanup (packages/core/src/session/run-coordinator.ts:107-108), and cleanup silently accepts a lost claim (packages/core/src/session/execution/local.ts:103-116). A finalizer lasting beyond the 30-second lease lets the recovery scanner take ownership (packages/core/src/session/recovery.ts:70-91), publish Step.Recovered (packages/core/src/session/recovery.ts:123-163), and enqueue a wake. When cleanup finishes, the old clear is ignored and the coordinator starts the queued successor. The cancelled Session then resumes. This is elapsed cleanup time, not the declared wall-clock-skew residual. The cancellation test blocks a finalizer but never advances past lease expiry or runs recovery (packages/core/test/session-execution-local.test.ts:200-238).
A failed provider stream can still commit Step.Ended, including phase continue. Hosted-tool reconciliation immediately before settlement only runs when stream._tag === "Success" (packages/core/src/session/runner/llm.ts:395-398). The code then commits Step.Ended whenever an in-memory step settlement exists and no provider-error event was observed (packages/core/src/session/runner/llm.ts:399-433), and propagates stream failure only afterward (packages/core/src/session/runner/llm.ts:436-438). A stream that emits step-finish and then dies can therefore bypass hosted-tool reconciliation and commit continuation. For typed LLMError, the earlier failure branch reconciles tools and fails the assistant, but this same ordering can still append Step.Ended ...
Date: Sep 16, 2026 * 🟡 (10:25) User supplied a truncated search result with 100 matches. The excerpt showed `packages/core/src/session/execution/local.ts:62-67` racing `input.run(claim, force, recovery?.continuationMessageID)` against repeated `SessionExecutionClaim.update(db, claim)` heartbeats spaced at `Duration.millis(leaseDuration / 3)`. * 🟡 (10:25) The supplied excerpt showed `packages/co…
Date: Sep 16, 2026 * 🔴 (10:20) User requires that session execution “never dispatches a provider after ownership changes”; coverage added in `packages/core/test/session-runner.test.ts:731`. * 🔴 (10:20) User requires that session execution “never publishes streamed output after ownership changes”; coverage added in `packages/core/test/session-runner.test.ts:757`. * 🔴 (10:20) User requires that …
Date: Sep 16, 2026 * 🟡 (10:18) At commit `ab9408c81cc9916177641ae8e9f08624fc27b5b9`, `packages/core/src/session/context-epoch.ts:28` wraps `initializeOnce(db, context, sessionID)` with `Effect.withSpan("SessionContextEpoch.initialize")`; line 37 wraps `prepareOnce(db, events, context, sessionID)` with `Effect.withSpan("SessionContextEpoch.prepare")`. * 🟡 (10:18) At commit `ab9408c81cc9916177641…
Date: Sep 16, 2026 * 🔴 [required-architecture-change] (10:17) User requires replacing local ownership with durable multi-node ownership when clustered; this remains unchecked (`- [ ]`) in the displayed implementation checklist at line 51. * 🟡 (10:16) Displayed session-input implementation imports `Admitted` and `Delivery` from `@opencode-ai/schema/session-input`, uses `SessionInputTable` and `S…
Date: Sep 16, 2026 * 🟡 (10:13) Test file imports `SessionRunCoordinator` from `@opencode-ai/core/session/run-coordinator`, uses `testEffect(Layer.empty)`, and defines a `"SessionRunCoordinator"` suite covering keyed execution behavior. * 🟡 (10:13) Test `"joins concurrent resumes for one key"` verifies that two concurrent `coordinator.run("session")` calls share one active drain execution: `runs…
Date: Sep 16, 2026 * 🟡 (10:11) Code shown exports `SessionRunCoordinator` from `"./run-coordinator"` and defines `Coordinator<Key, E>` with `active`, `run(key)`, `wake(key)`, and `interrupt(key)`; executions are serialized per key while different keys may run concurrently. * 🟡 (10:11) `run-coordinator` tracks each active key with `Entry<E>` fields `done: Deferred.Deferred<void, E>`, optional `o…
Date: Sep 16, 2026 * 🟡 (10:10) Assistant verified four newest fixes at their intended boundaries: 1. hosted-tool reconciliation now occurs before `Step.Ended`; 2. recovery identity is validated by both the scanner and exact promotion; 3. persisted attempt counters rotate failed/no-op wakes behind untouched Sessions; 4. coordinator interruption records the owner exit before cleanup, then settles …
Date: Sep 16, 2026 * 🔴 [requested-tests] (10:09) User supplied an Effect test in `packages/core/test/session-prompt.test.ts` asserting the invariant `"never dispatches a provider after ownership changes"`. * 🔴 [requested-tests] (10:09) User supplied an Effect test in `packages/core/test/session-prompt.test.ts` asserting the invariant `"never publishes streamed output after ownership changes"`; …
Date: Sep 16, 2026 * 🔴 [requested-tests] (10:09) User supplied an Effect test asserting the invariant `"never advances the Context Epoch after ownership changes"`; the scenario includes `owner_id: "stale-owner"`, a successor execution with `id: "successor-execution"`, `TestClock.adjust("1 second")`, and expects `Exit.isFailure(advanced)` to be `true`. * 🔴 [requested-tests] (10:09) User supplied…
Date: Sep 16, 2026 * 🔴 [requested-tests] (10:09) User supplied an Effect test asserting recovery behavior `"records a recovery continuation without forging an assistant identity"`, using execution ID `"continue-only"` and expecting exactly 1 row in `SessionRecoveryTable`. * 🔴 [requested-tests] (10:09) User supplied an Effect test asserting the invariant `"never recovers a live execution lease"`…
Date: Sep 16, 2026 * 🔴 [requested-tests] (10:09) User supplied an Effect test asserting the lease invariant `"never starts a second owner while the lease is live"`. * 🔴 [requested-tests] (10:09) User supplied an Effect test asserting the lease invariant `"never overwrites an expired lease"`. * 🔴 [requested-tests] (10:09) User supplied an Effect test asserting the lease invariant `"never re-ent…
Date: Sep 16, 2026 * 🔴 (10:09) User supplied changes to `packages/core/src/session/projector.ts`, including additional projection logic around `SessionEvent.Step.Started`, querying a `latest` record, and creating a synthetic continuation with `Prompt.make({ text: "continue" })`. * 🔴 (10:09) User supplied session memory-state update logic that assigns `tool.state = castDraft(...)` and ignores an…
Date: Sep 16, 2026 * 🔴 (10:09) User specified the session-execution claim invariant: an expired claim never renews itself. * 🔴 (10:09) User supplied session-execution claim logic using `Claim`, `Clock`, `Effect`, and a `where(claim, now)` predicate to constrain claim updates. * 🔴 (10:09) User supplied changes to `packages/core/src/session/execution/local.ts` adding `claim: SessionExecutionClai…
Date: Sep 16, 2026 * 🔴 (10:09) User supplied a patch excerpt updating `packages/core/schema.json` with schema entities for `session_execution` and `session_recovery`, including columns, foreign keys, primary keys, and indexes. * 🔴 (10:09) User supplied migration SQL creating the `session_execution` table with primary-key constraint `session_execution_pk` on `id`. * 🔴 (10:09) User supplied `ses…
Date: Sep 16, 2026 * 🔴 (10:06) User supplied SHA-256 artifacts: `/tmp/opencode/session-recovery-final-v4.patch` = `c2c8afb07a7b6a49c1d40c5515d718ad5a934c9291e80a068693259c26c566a0`; `/home/byk/Code/opencode/packages/opencode/dist/server/opencode-server` = `56c1bc48b6e21397db05eb0d41f73d243a4b64da093fcfbe209baed65457f6e8`. * 🔴 (10:07) User supplied commit hashes `ab9408c81cc9916177641ae8e9f08624…