Dashboard › opencode › Session 0mQxFApojYg9
0mQxFApojYg9V4pBePerform a final independent, adversarial correctness review of the exact immutable Session V2 recovery candidate. READ ONLY: do not modify repository files, refs, index, services, builds, tests, or artifacts. Base commit: ab9408c81cc9916177641ae8e9f08624fc27b5b9. Patch: /tmp/opencode/session-recovery-final-v6.patch. Expected patch SHA-256: a5c1bace440d1b6acb383c4fcffc52714a9373892e75b4f23bdbbf2dfdad8e16. Expected SEA artifact: /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server with SHA-256 d09c992a0f4cce15e73dd158fe60413420109c821afed6e121d11cdad9c7b583. Verify hashes, clean patch application to immutable base, ancestry, exact 33-file list, and every changed hunk from immutable objects/patch. Do not inspect mutable worktree as candidate evidence.
Review the complete architecture and every changed file for correctness, lifecycle, concurrency, replay, ordering, migration, API compatibility, and test gaps. The invariant is: never replay an uncertain provider request or old local tool call; reconcile unknown state and admit/promote one fresh deterministic queued {"text":"continue"} only when eligible. Never recover a live lease, renew expired ownership, overwrite any existing claim via ordinary acquisition, start a second owner while live, re-enter an uncertain live lease, recover superseded assistants, wake admit-only input, or allow recovery input into ordinary scheduling.
Focus on the two latest prior blockers and prove closure with file:line evidence: (1) Step.Interrupted must never delete/surrender the claim before SessionExecutionLocal coordinator cleanup and every runner/scope finalizer finish; cancellation heartbeat must preserve exact ownership through cleanup and never cancel cleanup if heartbeat/ownership fails; replacement acquisition while finalizer blocks must fail. (2) Step.Failed/provider failure must never set phase safe before local fibers and all unresolved hosted/local tool outcomes are durable; if tool reconciliation aborts ...
I will freeze the supplied base, patch, artifact, ancestry, and changed-path set first. I will then inspect only Git objects and the patch-derived candidate, trace every recovery invariant across all 33 files, and return severity-ordered findings without running or changing anything.
No files found
a5c1bace440d1b6acb383c4fcffc52714a9373892e75b4f23bdbbf2dfdad8e16 /tmp/opencode/session-recovery-final-v6.patch
d09c992a0f4cce15e73dd158fe60413420109c821afed6e121d11cdad9c7b583 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server
commit ab9408c81cc9916177641ae8e9f08624fc27b5b9 2e9407768d1401a25b69febb36657638a779053a d3323b963b9ef2de41a7ff9884299c74b93886ba 1789377773 feat(app): adapt vertical tab density
518 47 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 135 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 331 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 244 95 packages/core/src/session/runner/llm.ts 76 29 packages/core/src/session/runner/publish-llm-event.ts 68 1 packages/core/src/session/sql.ts 66 1 packages/core/test/database-migration.test.ts 283 0 packages/core/test/session-execution-local.test.ts 1780 3 packages/core/test/session-projector.test.ts 247 25 packages/core/test/session-p...
create mode 100644 packages/core/src/database/migration/20260914170650_session-recovery.ts create mode 100644 packages/core/src/session/recovery.ts create mode 100644 packages/core/test/session-execution-local.test.ts create mode 100644 packages/core/src/session/execution/claim.ts create mode 100644 packages/core/src/session/recovery-id.ts
drwxr-xr-x 9 byk byk 4096 Sep 16 13:19 /tmp/opencode
/tmp/opencode/session-recovery-review.1VQHG0
(no output)
(no output)
(no output)
<path>/tmp/opencode/session-recovery-final-v6.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/core/schema.json b/packages/core/schema.json
2: index d0eeeebd5c..dcc34e4652 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": "ec4e81da-4921-4488-85cd-745c52ddce17",
12: + "prevIds": [
13: + "f14a9b18-8207-487e-a3d3-227e629ba9ad"
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: ...
[
{
"content": "Verify immutable inputs, patch applicability, ancestry, and exact 33-file scope",
"status": "in_progress",
"priority": "high"
},
{
"content": "Trace execution claims, coordinator cleanup, interruption, provider failure, and tool reconciliation",
"status": "pending",
"priority": "high"
},
{
"content": "Trace recovery selection, promotion, replay, scheduling, fairness, and retry invariants",
"status": "pending",
"priority": "high"
},
{
"content": "Audit migration, generated artifacts, API layering, and all changed tests",
"status": "pending",
"priority": "high"
},
{
"content": "Reconcile findings against supplied validation and issue final merge verdict",
"status": "pending",
"priority": "high"
}
]
The patch and SEA hashes match exactly. The patch has 33 paths, applies cleanly to an archive of commit ab9408c81cc9916177641ae8e9f08624fc27b5b9, and adds five files; I am now tracing the two prior blockers through claim ownership, coordinator finalization, runner scope teardown, and tool settlement.
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/execution/claim.ts</path>
<type>file</type>
<content>
1: export * as SessionExecutionClaim from "./claim"
2:
3: import { and, eq, gt } from "drizzle-orm"
4: import { Clock, Effect } from "effect"
5: import type { Database } from "../../database/database"
6: import type { SessionSchema } from "../schema"
7: import { SESSION_EXECUTION_LEASE_MS, SessionExecutionTable } from "../sql"
8:
9: type DatabaseService = Database.Interface["db"]
10:
11: export type Claim = {
12: readonly sessionID: SessionSchema.ID
13: readonly executionID: string
14: readonly ownerID: string
15: }
16:
17: export type Values = Partial<Pick<typeof SessionExecutionTable.$inferInsert, "assistant_message_id" | "phase">>
18:
19: export class Lost extends Error {
20: constructor(readonly claim: Claim) {
21: super(`Session execution claim lost: ${claim.executionID}`)
22: }
23: }
24:
25: const where = (claim: Claim, now: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/execution/local.ts</path>
<type>file</type>
<content>
1: import { and, asc, eq, isNull } from "drizzle-orm"
2: import { Cause, Clock, Duration, Effect, Exit, Layer, Schedule } from "effect"
3: import { Database } from "../../database/database"
4: import { LocationServiceMap } from "../../location-service-map"
5: import { makeGlobalNode } from "../../effect/app-node"
6: import { SessionRunCoordinator } from "../run-coordinator"
7: import { SessionRunner } from "../runner"
8: import { SessionMessage } from "../message"
9: import { SessionSchema } from "../schema"
10: import { SessionStore } from "../store"
11: import { SessionExecution } from "../execution"
12: import { SessionExecutionClaim } from "./claim"
13: import { SESSION_EXECUTION_LEASE_MS, SessionExecutionTable, SessionRecoveryTable } from "../sql"
14:
15: /** Current-process routing for implicit-local Locations. Future remote placement belongs here. ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/run-coordinator.ts</path>
<type>file</type>
<content>
1: export * as SessionRunCoordinator from "./run-coordinator"
2:
3: import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
4:
5: /** Serializes execution for each key while allowing different keys to run concurrently. */
6: export interface Coordinator<Key, E> {
7: /** Snapshots keys with an execution owned by this coordinator. */
8: readonly active: Effect.Effect<ReadonlySet<Key>>
9: /** Starts execution while idle or joins the active execution. */
10: readonly run: (key: Key) => Effect.Effect<void, E>
11: /** Registers one coalesced follow-up after newly recorded work. */
12: readonly wake: (key: Key) => Effect.Effect<void>
13: /** Stops active execution, runs cleanup, then starts work that arrived while stopping. */
14: readonly interrupt: (key: Key, cleanup?: Effect.Effect<void>) => Effect.Effect<void>
15: }
16:
17: ...
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/runner/to-llm-message.ts:
Line 21: const toolInput = (tool: SessionMessage.AssistantTool) => {
Line 22: if (tool.state.status !== "pending") return tool.state.input
Line 24: return JSON.parse(tool.state.input) as unknown
Line 26: return tool.state.input
Line 30: const toolCall = (tool: SessionMessage.AssistantTool, providerMetadata: ProviderMetadata | undefined): ContentPart =>
Line 32: id: tool.id,
Line 33: name: tool.name,
Line 34: input: toolInput(tool),
Line 35: providerExecuted: tool.provider?.executed,
Line 39: const toolResult = (tool: SessionMessage.AssistantTool, providerMetadata: ProviderMetadata | undefined) => {
Line 40: if (tool.state.status === "completed") {
Line 44: tool.provider?.executed === true && tool.state.result !== undefined
Line 45: ? tool.state.result
Line 46: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
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 { and, eq } from "drizzle-orm"
13: import { AgentV2 } from "../../agent"
14: import { Config } from "../../config"
15: import { Database } from "../../database/database"
16: import { EventV2 } from "../../event"
17: import { Location } from "../../location"
18: import { ModelV2 } from "../../model"
19: import { PermissionV2 } from "../../permission"
20: import { ProviderV2 } from "../../provider"
21: import { QuestionV2 } from "../../question"
22: import { SystemContext } from "../../system-context/index"
23: import { SystemContextRegistry } from ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
1: import { ToolOutput, type LLMEvent, type ProviderMetadata, type ToolResultValue, type Usage } from "@opencode-ai/llm"
2: import { DateTime, Effect } from "effect"
3: import { EventV2 } from "../../event"
4: import { ModelV2 } from "../../model"
5: import { SessionEvent } from "../event"
6: import { SessionMessage } from "../message"
7: import { SessionSchema } from "../schema"
8:
9: type Input = {
10: readonly sessionID: SessionSchema.ID
11: readonly agent: string
12: readonly model: ModelV2.Ref
13: readonly snapshot?: string
14: readonly startCommit: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>
15: readonly eventCommit: () => Effect.Effect<void>
16: }
17:
18: const safe = (value: number | undefined) => Math.max(0, Number.isFinite(value) ? (value ?? ...
Found 1 matches /home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/FiberSet.ts: Line 680: export const join = <A, E>(self: FiberSet<A, E>): Effect.Effect<void, E> =>
<path>/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/FiberSet.ts</path>
<type>file</type>
<content>
640: * const program = Effect.gen(function*() {
641: * const set = yield* FiberSet.make()
642: *
643: * console.log(yield* FiberSet.size(set)) // 0
644: *
645: * // Add some fibers
646: * yield* FiberSet.run(set, Effect.never)
647: * yield* FiberSet.run(set, Effect.never)
648: *
649: * console.log(yield* FiberSet.size(set)) // 2
650: * })
651: * ```
652: *
653: * @category combinators
654: * @since 2.0.0
655: */
656: export const size = <A, E>(self: FiberSet<A, E>): Effect.Effect<number> =>
657: Effect.sync(() => self.state._tag === "Closed" ? 0 : self.state.backing.size)
658:
659: /**
660: * Joins all fibers in the FiberSet. If any fiber in the set terminates with a failure,
661: * the returned Effect will terminate with the first failure that occurred. ...
Found 3 matches
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-runner.test.ts:
Line 1988: SELECT RAISE(ABORT, 'crash before hosted tool reconciliation');
Line 2037: it.effect("keeps a provider defect after Step finish unknown", () =>
Line 2081: SELECT RAISE(ABORT, 'crash before provider tool reconciliation');
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1750: fragmentFixture("text", "text-summary", ["## Objective\n- Interrupted"]).completeEvents,
1751: ]
1752: const firstGate = yield* Deferred.make<void>()
1753: const summaryGate = yield* Deferred.make<void>()
1754: streamGate = firstGate
1755: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
1756: const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
1757: while (requests.length < 1) yield* Effect.yieldNow
1758: streamGate = summaryGate
1759: yield* Deferred.succeed(firstGate, undefined)
1760: while (requests.length < 2) yield* Effect.yieldNow
1761:
1762: yield* session.interrupt(sessionID)
1763: expect(yield* Fiber.await(run)).toMatchObject({ _tag: "Failure" })
1764: streamGate = undefined
1765: ...
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/event.test.ts:
Line 34: durable: {
Line 46: durable: {
Line 65: durable: {
Line 76: const durableData = (sessionID: Session.ID, text: string) => ({
Line 122: expect(event.durable?.version).toBe(2)
Line 126: it.effect("selects the latest durable definition independent of declaration order", () =>
Line 130: durable: { version: 2, aggregate: "id" },
Line 135: durable: { version: 1, aggregate: "id" },
Line 175: it.effect("commits local operational state inside a new durable event transaction", () =>
Line 192: it.effect("rolls back the durable event and projector when the local commit fails", () =>
Line 221: expect(String(exit)).toContain("Local commit hooks require a durable event")
Line 271: it.effect("isolates observer defects after durable events commit", () =>
Line 287: ...
Found 26 matches
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-runner.test.ts:
Line 833: it.effect("never fails interrupted tools after ownership changes", () =>
Line 1022: it.effect("interrupts a source Location runner after a Session moves", () =>
Line 1745: it.effect("interrupts overflow recovery while the summary provider is running", () =>
Line 2608: it.effect("preserves durable queued input for a later wake after interruption", () =>
Line 2651: it.effect("preserves durable steering input for a later resume after interruption", () =>
Line 3405: it.effect("interrupts runner continuation when permission approval is declined", () =>
Line 3510: it.effect("interrupts runner continuation when a question is dismissed", () =>
Line 3600: it.effect("durably fails blocked local tools when a provider turn is interrupted", () =>
Line 3661: it.effect("retains the claim after Step interruption until runner finalizers finish", ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-execution-local.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { Deferred, Effect, Exit, Fiber, Scope } from "effect"
3: import { TestClock } from "effect/testing"
4: import { eq } from "drizzle-orm"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { Project } from "@opencode-ai/core/project"
7: import { ProjectTable } from "@opencode-ai/core/project/sql"
8: import { AbsolutePath } from "@opencode-ai/core/schema"
9: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
10: import { SessionSchema } from "@opencode-ai/core/session/schema"
11: import { SessionExecutionTable, SessionTable } from "@opencode-ai/core/session/sql"
12: import { testEffect } from "./lib/effect"
13:
14: const sessionID = SessionSchema.ID.make("ses_execution_local")
15: const it = ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3320: LLMEvent.finish({ reason: "tool-calls" }),
3321: ],
3322: [
3323: LLMEvent.stepStart({ index: 0 }),
3324: LLMEvent.textStart({ id: "text-after-defect" }),
3325: LLMEvent.textDelta({ id: "text-after-defect", text: "Recovered" }),
3326: LLMEvent.textEnd({ id: "text-after-defect" }),
3327: LLMEvent.stepFinish({ index: 0, reason: "stop" }),
3328: LLMEvent.finish({ reason: "stop" }),
3329: ],
3330: ]
3331:
3332: yield* session.resume(sessionID)
3333:
3334: expect(requests).toHaveLength(2)
3335: expect(requests[1]?.messages.map((message) => message.role)).toEqual(["user", "assistant", "tool"])
3336: expect(yield* session.context(sessionID)).toMatchObject([
3337: { type: "user", text: "Call defect" },
3338: {
3339: ...
Found 5 matches
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/tool/tool.ts:
Line 66: readonly settle: (call: ToolCall, context: Context) => Effect.Effect<ToolOutput, ToolFailure>
Line 150: export const settle = (tool: AnyTool, call: ToolCall, context: Context) => runtimeOf(tool).settle(call, context)
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/tool/registry.ts:
Line 31: readonly settle: (input: ExecuteInput) => Effect.Effect<Settlement, ToolOutputStore.Error>
Line 50: const settleWith = Effect.fn("ToolRegistry.settle")(function* (input: ExecuteInput, advertised?: object) {
Line 62: const pending = yield* settle(registration.tool, input.call, {
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/tool/registry.ts</path>
<type>file</type>
<content>
1: export * as ToolRegistry from "./registry"
2:
3: import { ToolOutput, type ToolCall, type ToolDefinition, type ToolResultValue } from "@opencode-ai/llm"
4: import { Context, Effect, Layer, Scope } from "effect"
5: import { AgentV2 } from "../agent"
6: import { PermissionV2 } from "../permission"
7: import { SessionMessage } from "../session/message"
8: import { SessionSchema } from "../session/schema"
9: import { ToolOutputStore } from "../tool-output-store"
10: import { Wildcard } from "../util/wildcard"
11: import { ApplicationTools } from "./application-tools"
12: import { definition, permission, settle, validateName, type AnyTool, type RegistrationError } from "./tool"
13: import { Tools } from "./tools"
14: import { makeLocationNode } from "../effect/app-node"
15:
16: export type ExecuteInput = {
17: readonly sessionID: SessionSchema.ID
18: readonly ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/tool/tool.ts</path>
<type>file</type>
<content>
110: ),
111: ),
112: ),
113: Effect.map(({ output, structured }) => ({
114: structured,
115: content:
116: config.toModelOutput?.({ input, output }).map((part) =>
117: part.type === "text"
118: ? { type: "text" as const, text: part.text }
119: : {
120: type: "file" as const,
121: uri: `data:${part.mime};base64,${part.data}`,
122: mime: part.mime,
123: name: part.name,
124: },
125: ) ?? (typeof output === "string" ? ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/tool/tool.ts</path>
<type>file</type>
<content>
1: export * as Tool from "./tool"
2:
3: import { ToolDefinition, ToolFailure, ToolOutput, type ToolCall } from "@opencode-ai/llm"
4: import { Effect, JsonSchema, Schema } from "effect"
5: import type { AgentV2 } from "../agent"
6: import type { SessionMessage } from "../session/message"
7: import type { SessionSchema } from "../session/schema"
8:
9: export interface Context {
10: readonly sessionID: SessionSchema.ID
11: readonly agent: AgentV2.ID
12: readonly assistantMessageID: SessionMessage.ID
13: readonly toolCallID: string
14: }
15:
16: export type SchemaType<A> = Schema.Codec<A, any, never, never>
17:
18: declare const TypeId: unique symbol
19:
20: export interface Definition<Input extends SchemaType<any>, Output extends SchemaType<any>> {
21: readonly [TypeId]: {
22: readonly _Input: Input
23: readonly _Output: Output
24: }
25: }
26:
27: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, asc, desc, eq, exists, gt, isNull, lte, or, sql } from "drizzle-orm"
4: import { Clock, DateTime, Effect, Layer, Option, Schedule, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionProjector } from "./projector"
11: import { SessionExecutionClaim } from "./execution/claim"
12: import { SessionInput } from "./input"
13: import { SessionSchema } from "./schema"
14: import {
15: SESSION_EXECUTION_LEASE_MS,
16: SessionExecutionTable,
17: SessionInputTable,
18: SessionRecoveryTable,
19: SessionMessageTable,
20: SessionTable,
21: } from "./sql"
22:
23: export ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
1: export * as SessionInput from "./input"
2:
3: import { and, asc, desc, eq, isNull, lte } from "drizzle-orm"
4: import { DateTime, Effect, Option, Schema } from "effect"
5: import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
6: import type { Database } from "../database/database"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionMessage } from "./message"
10: import { Prompt } from "./prompt"
11: import { continuation, message } from "./recovery-id"
12: import { SessionSchema } from "./schema"
13: import { SessionInputTable, SessionMessageTable, SessionRecoveryTable } from "./sql"
14:
15: type DatabaseService = Database.Interface["db"]
16:
17: export { Admitted, Delivery }
18:
19: const decodePrompt = Schema.decodeUnknownSync(Prompt)
20: const encodePrompt = Schema.encodeSync(Prompt)
21: const ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
498: export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
499: db: DatabaseService,
500: events: EventV2.Interface,
501: sessionID: SessionSchema.ID,
502: cutoff: number,
503: commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
504: ) {
505: const rows = yield* db
506: .select({ input: SessionInputTable })
507: .from(SessionInputTable)
508: .leftJoin(
509: SessionRecoveryTable,
510: eq(SessionRecoveryTable.continuation_message_id, SessionInputTable.id),
511: )
512: .where(
513: and(
514: eq(SessionInputTable.session_id, sessionID),
515: isNull(SessionInputTable.promoted_seq),
516: eq(SessionInputTable.delivery, "steer"),
517: lte(SessionInputTable.admitted_seq, cutoff),
518: isNull(SessionRecoveryTable.execution_id),
519: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
1: export * as SessionProjector from "./projector"
2:
3: import { and, desc, eq, gt, or, sql } from "drizzle-orm"
4: import { DateTime, Effect, Layer, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { makeGlobalNode } from "../effect/app-node"
8: import { SessionEvent } from "./event"
9: import { SessionV1 } from "../v1/session"
10: import { WorkspaceTable } from "../control-plane/workspace.sql"
11: import { SessionMessage } from "./message"
12: import { SessionMessageUpdater } from "./message-updater"
13: import { SessionInput } from "./input"
14: import { Prompt } from "./prompt"
15: import { continuation } from "./recovery-id"
16: import { WorkspaceV2 } from "../workspace"
17: import {
18: MessageTable,
19: PartTable,
20: SessionInputTable,
21: SessionMessageTable,
22: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
1: import { castDraft, produce, type WritableDraft } from "immer"
2: import { Effect } from "effect"
3: import { SessionEvent } from "./event"
4: import { SessionMessage } from "./message"
5: import { message } from "./recovery-id"
6:
7: export type MemoryState = {
8: messages: SessionMessage.Message[]
9: }
10:
11: export interface Adapter {
12: readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined>
13: readonly getAssistant: (messageID: SessionMessage.ID) => Effect.Effect<SessionMessage.Assistant | undefined>
14: readonly getCurrentShell: (callID: string) => Effect.Effect<SessionMessage.Shell | undefined>
15: readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect<void>
16: readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect<void>
17: readonly appendMessage: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/context-epoch.ts</path>
<type>file</type>
<content>
1: export * as SessionContextEpoch from "./context-epoch"
2:
3: import { eq } from "drizzle-orm"
4: import { DateTime, Effect, Schema } from "effect"
5: import type { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { SystemContext } from "../system-context/index"
8: import { ContextSnapshotDecodeError } from "./error"
9: import { SessionEvent } from "./event"
10: import { SessionHistory } from "./history"
11: import { SessionInput } from "./input"
12: import { SessionMessage } from "./message"
13: import { SessionSchema } from "./schema"
14: import { SessionContextEpochTable } from "./sql"
15: import { SessionExecutionClaim } from "./execution/claim"
16:
17: type DatabaseService = Database.Interface["db"]
18:
19: interface Prepared {
20: readonly baseline: string
21: readonly baselineSeq: number
22: }
23:
24: ...
Found 33 matches /tmp/opencode/session-recovery-final-v6.patch: Line 1: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 866: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 877: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 938: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 1036: diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts Line 1068: diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts Line 1157: diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts Line 1333: diff --git a/packages/core/src/session/execution/local.ts b/packages/core/src/session/execution/local.ts Line 1519: diff --git ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/compaction.ts</path>
<type>file</type>
<content>
1: export * as SessionCompaction from "./compaction"
2:
3: import { LLM, LLMError, LLMEvent, Message, type LLMRequest, type Model } from "@opencode-ai/llm"
4: import { DateTime, Effect, Stream } from "effect"
5: import type { Config } from "../config"
6: import type { EventV2 } from "../event"
7: import { SessionEvent } from "./event"
8: import { SessionMessage } from "./message"
9: import { SessionSchema } from "./schema"
10: import { Token } from "../util/token"
11:
12: const DEFAULT_BUFFER = 20_000
13: const DEFAULT_KEEP_TOKENS = 8_000
14: const TOOL_OUTPUT_MAX_CHARS = 2_000
15: const SUMMARY_OUTPUT_TOKENS = 4_096
16: const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response. ...
Found 36 matches
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/execution/claim.ts:
Line 17: export type Values = Partial<Pick<typeof SessionExecutionTable.$inferInsert, "assistant_message_id" | "phase">>
Line 27: eq(SessionExecutionTable.session_id, claim.sessionID),
Line 28: eq(SessionExecutionTable.id, claim.executionID),
Line 29: eq(SessionExecutionTable.owner_id, claim.ownerID),
Line 30: gt(SessionExecutionTable.expires_at, now),
Line 45: .update(SessionExecutionTable)
Line 48: .returning({ id: SessionExecutionTable.id })
Line 57: .delete(SessionExecutionTable)
Line 59: .returning({ id: SessionExecutionTable.id })
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/execution/local.ts:
Line 32: .insert(SessionExecutionTable)
Line 42: .returning({ id: SessionExecutionTable.id })
Line 84: .select({ phase: SessionExecutionTable.phase })
Line ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session.ts</path>
<type>file</type>
<content>
1: export * as SessionV2 from "./session"
2: export * from "./session/schema"
3:
4: import { DateTime, Effect, Layer, Schema, Context, Stream } from "effect"
5: import { ListAnchor } from "@opencode-ai/schema/session"
6: import { and, asc, desc, eq, gt, like, lt, or, type SQL } from "drizzle-orm"
7: import { ProjectV2 } from "./project"
8: import { WorkspaceV2 } from "./workspace"
9: import { ModelV2 } from "./model"
10: import { Location } from "./location"
11: import { SessionMessage } from "./session/message"
12: import { Prompt } from "./session/prompt"
13: import { PromptInput } from "@opencode-ai/schema/prompt-input"
14: import { EventV2 } from "./event"
15: import { Database } from "./database/database"
16: import { SessionProjector } from "./session/projector"
17: import { SessionMessageTable, SessionTable } from "./session/sql"
18: import { SessionSchema } from ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session.ts</path>
<type>file</type>
<content>
300: Effect.orDie,
301: )
302: return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row))
303: }),
304: messages: Effect.fn("V2Session.messages")(function* (input) {
305: yield* result.get(input.sessionID)
306: const direction = input.cursor?.direction ?? "next"
307: const requestedOrder = input.order ?? "desc"
308: const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
309: const anchor = input.cursor
310: ? yield* db
311: .select({ seq: SessionMessageTable.seq })
312: .from(SessionMessageTable)
313: .where(
314: and(eq(SessionMessageTable.session_id, input.sessionID), eq(SessionMessageTable.id, input.cursor.id)),
315: )
316: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
1: export * as SessionEvent from "./session-event"
2:
3: import { Schema } from "effect"
4: import { optional } from "./schema"
5: import { Event } from "./event"
6: import { ProviderMetadata, ToolContent } from "./llm"
7: import { Delivery } from "./session-delivery"
8: import { Model } from "./model"
9: import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "./schema"
10: import { FileAttachment, Prompt } from "./prompt"
11: import { SessionID } from "./session-id"
12: import { Location } from "./location"
13: import { SessionMessage } from "./session-message"
14: import { Revert } from "./revert"
15:
16: export { FileAttachment }
17:
18: export const Source = Schema.Struct({
19: start: NonNegativeInt,
20: end: NonNegativeInt,
21: text: Schema.String,
22: }).annotate({
23: identifier: "session.next.event.source",
24: })
25: export ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/sql.ts</path>
<type>file</type>
<content>
1: import { sql } from "drizzle-orm"
2: import { check, sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from "drizzle-orm/sqlite-core"
3: import * as DatabasePath from "../database/path"
4: import { ProjectTable } from "../project/sql"
5: import type { SessionMessage } from "./message"
6: import type { Prompt } from "./prompt"
7: import type { SessionInput } from "./input"
8: import type { Snapshot } from "../snapshot"
9: import { PermissionV1 } from "../v1/permission"
10: import { ProjectV2 } from "../project"
11: import type { SessionSchema } from "./schema"
12: import type { MessageID, PartID, SessionV1 } from "../v1/session"
13: import { WorkspaceV2 } from "../workspace"
14: import { Timestamps } from "../database/schema.sql"
15: import type { SystemContext } from "../system-context/index"
16: import { AgentV2 } from "../agent"
17: import type { ...
Found 1 matches /tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/projector.ts: Line 504: .delete(SessionMessageTable)
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
488: )
489: yield* events.project(SessionEvent.RevertEvent.Committed, (event) =>
490: Effect.gen(function* () {
491: const boundary = yield* db
492: .select({ seq: SessionMessageTable.seq })
493: .from(SessionMessageTable)
494: .where(
495: and(
496: eq(SessionMessageTable.session_id, event.data.sessionID),
497: eq(SessionMessageTable.id, event.data.messageID),
498: ),
499: )
500: .get()
501: .pipe(Effect.orDie)
502: if (!boundary) return yield* Effect.die(`Revert boundary message not found: ${event.data.messageID}`)
503: yield* db
504: .delete(SessionMessageTable)
505: .where(
506: and(eq(SessionMessageTable.session_id, event.data.sessionID), gt(SessionMessageTable.seq, ...
Found 4 matches /tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-projector.test.ts: Line 158: yield* db.delete(SessionMessageTable).where(eq(SessionMessageTable.session_id, sessionID)).run() Line 197: yield* db.delete(SessionMessageTable).where(eq(SessionMessageTable.session_id, sessionID)).run() /tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-runner.test.ts: Line 415: yield* db.delete(SessionMessageTable).where(eq(SessionMessageTable.session_id, id)).run().pipe(Effect.orDie) /tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-prompt.test.ts: Line 114: yield* db.delete(SessionMessageTable).where(eq(SessionMessageTable.session_id, sessionID)).run().pipe(Effect.orDie)
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/database/migration/20260914170650_session-recovery.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "../migration"
3:
4: export default {
5: id: "20260914170650_session-recovery",
6: up(tx) {
7: return Effect.gen(function* () {
8: yield* tx.run(`ALTER TABLE \`session_input\` ADD \`wake_pending\` integer DEFAULT false NOT NULL;`)
9: yield* tx.run(`ALTER TABLE \`session\` ADD \`wake_attempts\` integer DEFAULT 0 NOT NULL;`)
10: yield* tx.run(`
11: CREATE TABLE \`session_execution\` (
12: \`id\` text NOT NULL,
13: \`session_id\` text NOT NULL,
14: \`owner_id\` text NOT NULL,
15: \`assistant_message_id\` text,
16: \`phase\` text NOT NULL,
17: \`time_created\` integer NOT NULL,
18: \`expires_at\` integer NOT NULL,
19: \`recovery_error\` ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/server/src/routes.ts</path>
<type>file</type>
<content>
1: import { Database } from "@opencode-ai/core/database/database"
2: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
3: import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
4: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
5: import { EventV2 } from "@opencode-ai/core/event"
6: import { Credential } from "@opencode-ai/core/credential"
7: import { PermissionSaved } from "@opencode-ai/core/permission/saved"
8: import { PtyTicket } from "@opencode-ai/core/pty/ticket"
9: import { SessionV2 } from "@opencode-ai/core/session"
10: import { SessionExecution } from "@opencode-ai/core/session/execution"
11: import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
12: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
13: import { SessionRecovery } from ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/opencode/src/server/routes/instance/httpapi/server.ts</path>
<type>file</type>
<content>
1: import { Config as EffectConfig, Context, Effect, Layer } from "effect"
2: import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi"
3: import { HttpClient, HttpMiddleware, HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
4: import * as Socket from "effect/unstable/socket/Socket"
5: import { FSUtil } from "@opencode-ai/core/fs-util"
6: import * as Observability from "@opencode-ai/core/observability"
7: import { Account } from "@/account/account"
8: import { Agent } from "@/agent/agent"
9: import { Auth } from "@/auth"
10: import { BackgroundJob } from "@/background/job"
11: import { Command } from "@/command"
12: import { Config } from "@/config/config"
13: import { Workspace } from "@/control-plane/workspace"
14: import { Env } from "@/env"
15: import { EventV2Bridge } from "@/event-v2-bridge"
16: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/opencode/src/server/routes/instance/httpapi/server.ts</path> <type>file</type> <content> 210: | HttpRouter.Request<"Requires", unknown> 211: | HttpRouter.Request<"GlobalRequires", never> 212: 213: const app = LayerNode.group([ 214: Npm.node, 215: FSUtil.node, 216: Database.node, 217: Auth.node, 218: Account.node, 219: Config.node, 220: Env.node, 221: Git.node, 222: Ripgrep.node, 223: Storage.node, 224: Snapshot.node, 225: Plugin.node, 226: ModelsDev.node, 227: Provider.node, 228: ProviderAuth.node, 229: Agent.node, 230: Skill.node, 231: Discovery.node, 232: Question.node, 233: Permission.node, 234: PermissionSaved.node, 235: Todo.node, 236: Session.node, 237: SessionProjector.node, 238: SessionStatus.node, 239: BackgroundJob.node, 240: RuntimeFlags.node, 241: EventV2Bridge.node, 242: SessionRunState.node, 243: SessionProcessor.node, 244: ...
Found 2 matches
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session.ts:
Line 12: import { Prompt } from "./session/prompt"
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/runner/llm.ts:
Line 51: * `SessionPrompt` monolith. Implement the unchecked items in small reviewed slices:
Your prior response was empty and is invalid. Resume the SAME review and return a substantive evidence-based report now. If any tool/path/revision is unavailable, quote the exact blocker and command/error; do not return progress or an empty result. Re-verify exact base ab9408c81cc9916177641ae8e9f08624fc27b5b9, patch /tmp/opencode/session-recovery-final-v6.patch SHA-256 a5c1bace440d1b6acb383c4fcffc52714a9373892e75b4f23bdbbf2dfdad8e16, SEA SHA-256 d09c992a0f4cce15e73dd158fe60413420109c821afed6e121d11cdad9c7b583, clean apply, and 33 files. Complete the read-only correctness review requested previously, with special focus on cancellation ownership through all finalizers, heartbeat-loss behavior, provider/tool reconciliation before safe settlement, post-StepFinished defects, CAS no-wake race, persisted fairness across restart/fresh admissions, replay/quarantine, schema lineage, generated contracts, and no SessionPrompt/dependency inversion. Negative-control evidence now available: rejected v5 fails keeps provider failure unknown until unresolved tools are durable because the claim is absent instead of unknown; fails retains the claim after Step interruption until runner finalizers finish by 5s timeout while replacement exclusion is checked; fails never wakes work settled between selection and redrive CAS by waking ses_ordinary_settled_at_cas; keeps a provider defect after Step finish unknown already passed v5 and is retained coverage, not fail-first evidence. Return findings ordered MUST-FIX, CONCERN, PASS with exact patched file:line evidence. End exactly MERGE or DO-NOT-MERGE.
I am resuming from the same immutable archive-derived candidate. The prior empty response resulted from a failed memory lookup, not a repository or revision blocker; I will re-verify the frozen inputs and finish the source review without using mutable worktree state.
a5c1bace440d1b6acb383c4fcffc52714a9373892e75b4f23bdbbf2dfdad8e16 /tmp/opencode/session-recovery-final-v6.patch
d09c992a0f4cce15e73dd158fe60413420109c821afed6e121d11cdad9c7b583 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server
commit ab9408c81cc9916177641ae8e9f08624fc27b5b9 2e9407768d1401a25b69febb36657638a779053a d3323b963b9ef2de41a7ff9884299c74b93886ba feat(app): adapt vertical tab density
error: patch failed: packages/core/schema.json:1 error: packages/core/schema.json: patch does not apply error: patch failed: packages/core/src/database/migration.gen.ts:40 error: packages/core/src/database/migration.gen.ts: patch does not apply error: packages/core/src/database/migration/20260914170650_session-recovery.ts: already exists in working directory error: patch failed: packages/core/src/database/schema.gen.ts:154 error: packages/core/src/database/schema.gen.ts: patch does not apply error: patch failed: packages/core/src/session.ts:365 error: packages/core/src/session.ts: patch does not apply error: patch failed: packages/core/src/session/compaction.ts:73 error: packages/core/src/session/compaction.ts: patch does not apply error: patch failed: packages/core/src/session/context-epoch.ts:12 error: packages/core/src/session/context-epoch.ts: patch does not apply error: patch failed: packages/core/src/session/execution/local.ts:1 error: ...
/tmp/opencode/session-recovery-recheck.f3rLnS
(no output)
(no output)
33
Found 9 matches /tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/execution/claim.ts: Line 35: * maximum clock skew plus one renewal interval. Execution IDs fence a node immediately after takeover; /tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/run-coordinator.ts: Line 13: /** Stops active execution, runs cleanup, then starts work that arrived while stopping. */ Line 14: readonly interrupt: (key: Key, cleanup?: Effect.Effect<void>) => Effect.Effect<void> Line 99: const interrupt = (key: Key, cleanup = Effect.void): Effect.Effect<void> => Line 103: if (entry === undefined || owner === undefined) return cleanup Line 108: const cleanupExit = yield* Effect.exit(cleanup) Line 122: if (Exit.isFailure(cleanupExit)) return yield* Effect.failCause(cleanupExit.cause) /tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/runner/llm.ts: Line 87: * - [ ] Update title, ...
Found 19 matches
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-runner.test.ts:
Line 703: it.effect("redrives an ordinary prompt after its advisory wake is lost", () =>
Line 1213: it.effect("keeps the sampled agent when selection changes during observation", () =>
Line 1245: it.effect("keeps the sampled model when selection changes during model resolution", () =>
Line 2608: it.effect("preserves durable queued input for a later wake after interruption", () =>
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-projector.test.ts:
Line 389: it.effect("redrives a durable recovery wake after admission", () =>
Line 440: it.effect("quarantines a malformed recovery row without blocking a valid wake", () =>
Line 494: it.effect("quarantines a full batch of poison recovery rows before redriving valid work", () =>
Line 559: it.effect("fairly redrives recovery and ordinary work beyond a full bounded batch", () ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
360: .run()
361: yield* db
362: .insert(SessionMessageTable)
363: .values([
364: assistantRow(abandonedID, 0),
365: assistantRow(SessionMessage.ID.make("msg_recovery_newer"), 1, { created, completed: created }),
366: ])
367: .run()
368: yield* db
369: .insert(SessionExecutionTable)
370: .values({
371: id: "execution",
372: session_id: sessionID,
373: owner_id: "dead",
374: assistant_message_id: abandonedID,
375: phase: "unknown",
376: time_created: 0,
377: expires_at: 0,
378: })
379: .run()
380:
381: yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, recoveryExecution))
382:
383: expect(yield* ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/schema/test/event-manifest.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { FileSystem, Integration, Permission, Project, Reference, Session, Workspace } from "../src"
3: import { EventManifest } from "../src/event-manifest"
4: import { IdeEvent } from "../src/ide-event"
5: import { SessionEvent } from "../src/session-event"
6: import { SessionTodo } from "../src/session-todo"
7: import { SessionV1 } from "../src/session-v1"
8: import { WorkspaceEvent } from "../src/workspace-event"
9:
10: describe("public event manifest", () => {
11: test("owns the complete public event surface", () => {
12: expect(EventManifest.ServerDefinitions.length).toBe(61)
13: expect(EventManifest.Definitions.length).toBe(91)
14: expect(SessionV1.Event.Definitions).toEqual([
15: SessionV1.Event.Created,
16: SessionV1.Event.Updated,
17: ...
Found 4 matches
/tmp/opencode/session-recovery-review.1VQHG0/packages/schema/src/session-event.ts:
Line 104: export const PromptExecutionRequested = Event.define({
Line 112: export type PromptExecutionRequested = typeof PromptExecutionRequested.Type
Line 490: PromptExecutionRequested,
Line 524: PromptExecutionRequested,
Found 2 matches /tmp/opencode/session-recovery-review.1VQHG0/packages/schema/src/session-event.ts: Line 105: type: "session.next.prompt.execution_requested", Line 221: type: "session.next.step.recovered",
/tmp/opencode/session-recovery-review.1VQHG0/packages/schema/src/event-manifest.ts /tmp/opencode/session-recovery-review.1VQHG0/packages/schema/src/durable-event-manifest.ts
No files found
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/schema/src/durable-event-manifest.ts</path>
<type>file</type>
<content>
1: export * as DurableEventManifest from "./durable-event-manifest"
2:
3: import { Event } from "./event"
4: import { SessionEvent } from "./session-event"
5: import { SessionV1 } from "./session-v1"
6:
7: export const SessionDurable = {
8: definitions: Event.durable(SessionEvent.DurableDefinitions),
9: schema: SessionEvent.Durable,
10: } as const
11:
12: export const Durable = Event.durable([
13: ...SessionV1.Event.Definitions.filter((definition) => definition.durable !== undefined),
14: ...SessionEvent.DurableDefinitions,
15: ])
(End of file - total 15 lines)
</content>
Found 2 matches
/tmp/opencode/session-recovery-review.1VQHG0/packages/schema/src/event.ts:
Line 66: ...(input.durable === undefined ? {} : { durable: input.durable }),
Line 98: export function durable<const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) {
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/schema/src/event.ts</path>
<type>file</type>
<content>
40: }
41:
42: export function define<
43: const Type extends string,
44: const Fields extends Readonly<Record<PropertyKey, Schema.Codec<unknown, unknown>>>,
45: >(input: {
46: readonly type: Type
47: readonly durable?: {
48: readonly version: number
49: readonly aggregate: string
50: }
51: readonly schema: Fields
52: }) {
53: const data = Schema.Struct(input.schema)
54: return Schema.Struct({
55: id: ID,
56: metadata: optional(Schema.Record(Schema.String, Schema.Unknown)),
57: type: Schema.Literal(input.type),
58: durable: optional(Schema.Struct({ aggregateID: Schema.String, seq: Schema.Int, version: Schema.Int })),
59: location: optional(Location.Ref),
60: data,
61: })
62: .annotate({ identifier: input.type })
63: .pipe(
64: statics(() => ({
65: type: input.type,
66: ...(input.durable === ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
450: messageID: SessionMessage.ID,
451: text: Schema.String,
452: },
453: })
454: export type Delta = typeof Delta.Type
455:
456: export const Ended = Event.define({
457: type: "session.next.compaction.ended",
458: ...options,
459: schema: {
460: ...Base,
461: messageID: SessionMessage.ID,
462: reason: Started.data.fields.reason,
463: text: Schema.String,
464: recent: Schema.String,
465: },
466: })
467: export type Ended = typeof Ended.Type
468: }
469:
470: export namespace RevertEvent {
471: export const Staged = Event.define({
472: type: "session.next.revert.staged",
473: ...options,
474: schema: { ...Base, revert: Revert.State },
475: })
476: export const Cleared = Event.define({ type: "session.next.revert.cleared", ...options, schema: Base })
477: export const ...
export * as SessionEvent from "./session-event"
import { Schema } from "effect"
import { optional } from "./schema"
import { Event } from "./event"
import { ProviderMetadata, ToolContent } from "./llm"
import { Delivery } from "./session-delivery"
import { Model } from "./model"
import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "./schema"
import { FileAttachment, Prompt } from "./prompt"
import { SessionID } from "./session-id"
import { Location } from "./location"
import { SessionMessage } from "./session-message"
import { Revert } from "./revert"
export { FileAttachment }
export const Source = Schema.Struct({
start: NonNegativeInt,
end: NonNegativeInt,
text: Schema.String,
}).annotate({
identifier: "session.next.event.source",
})
export interface Source extends Schema.Schema.Type<typeof Source> {}
const Base = {
timestamp: DateTimeUtcFromMillis,
sessionID: SessionID,
}
const PromptFields = {
...Base,
messageID: SessionMessage.ID,
prompt: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/database-migration.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { $ } from "bun"
3: import { fileURLToPath } from "url"
4: import path from "path"
5: import { SqliteClient } from "@effect/sql-sqlite-bun"
6: import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
7: import { Effect, Exit, Layer } from "effect"
8: import { eq, inArray, sql } from "drizzle-orm"
9: import { DatabaseMigration } from "@opencode-ai/core/database/migration"
10: import { migrations } from "@opencode-ai/core/database/migration.gen"
11: import workspaceNameMigration from "@opencode-ai/core/database/migration/20260410174513_workspace-name"
12: import sessionUsageMigration from "@opencode-ai/core/database/migration/20260510033149_session_usage"
13: import normalizeStoragePathsMigration from ...
Found 16 matches /tmp/opencode/session-recovery-review.1VQHG0/packages/sdk/js/src/v2/gen/types.gen.ts: Line 874: requestExecution?: boolean Line 879: type: "session.next.prompt.execution_requested" Line 973: type: "session.next.step.interrupted" Line 983: type: "session.next.step.recovered" Line 3430: requestExecution?: boolean Line 3439: type: "session.next.prompt.execution_requested.1" Line 3589: type: "session.next.step.interrupted.1" Line 3606: type: "session.next.step.recovered.1" Line 4362: requestExecution?: boolean Line 4371: type: "session.next.prompt.execution_requested" Line 4545: type: "session.next.step.interrupted" Line 4565: type: "session.next.step.recovered" Line 6464: requestExecution?: boolean Line 6470: type: "session.next.prompt.execution_requested" Line 6572: type: "session.next.step.interrupted" Line 6583: type: "session.next.step.recovered"
Found 1 matches /home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/FiberSet.ts: Line 414: export const clear = <A, E>(self: FiberSet<A, E>): Effect.Effect<void> =>
<path>/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/FiberSet.ts</path>
<type>file</type>
<content>
388: * Interrupts all fibers in the `FiberSet` and clears the set.
389: *
390: * **Example** (Clearing all fibers)
391: *
392: * ```ts
393: * import { Effect, FiberSet } from "effect"
394: *
395: * const program = Effect.gen(function*() {
396: * const set = yield* FiberSet.make()
397: *
398: * // Add some fibers
399: * yield* FiberSet.run(set, Effect.never)
400: * yield* FiberSet.run(set, Effect.never)
401: *
402: * console.log(yield* FiberSet.size(set)) // 2
403: *
404: * // Clear all fibers
405: * yield* FiberSet.clear(set)
406: *
407: * console.log(yield* FiberSet.size(set)) // 0
408: * })
409: * ```
410: *
411: * @category combinators
412: * @since 2.0.0
413: */
414: export const clear = <A, E>(self: FiberSet<A, E>): Effect.Effect<void> =>
415: Effect.suspend(() => {
416: if (self.state._tag ...
Found 4 matches /tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-runner.test.ts: Line 75: let runnerFinalizer = Effect.void Line 251: Effect.ensuring(Effect.suspend(() => runnerFinalizer)), Line 359: runnerFinalizer = Effect.void Line 3669: runnerFinalizer = Deferred.succeed(finalizerStarted, undefined).pipe(Effect.andThen(Deferred.await(releaseFinalizer)))
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
220: Effect.succeed([
221: new Config.Document({
222: type: "document",
223: info: new Config.Info({
224: compaction: new ConfigCompaction.Info({
225: buffer: 3_000,
226: keep: new ConfigCompaction.Keep({ tokens: 1_000 }),
227: }),
228: }),
229: }),
230: ]),
231: }),
232: )
233: const runnerLayer = AppNodeBuilder.build(SessionRunnerLLM.node, [
234: [Snapshot.node, Snapshot.noopLayer],
235: [LayerNodePlatform.llmClient, client],
236: [SessionRunnerModel.node, models],
237: [SystemContextRegistry.node, systemContext],
238: [Location.node, Location.boundNode({ directory: AbsolutePath.make("/project") })],
239: [SkillGuidance.node, skillGuidance],
240: [ReferenceGuidance.node, referenceGuidance],
241: [PermissionV2.node, ...
<path>/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/FiberSet.ts</path>
<type>file</type>
<content>
50: * @since 2.0.0
51: */
52: export interface FiberSet<out A = unknown, out E = unknown>
53: extends Pipeable, Inspectable.Inspectable, Iterable<Fiber.Fiber<A, E>>
54: {
55: readonly [TypeId]: typeof TypeId
56: readonly deferred: Deferred.Deferred<void, unknown>
57: state: {
58: readonly _tag: "Open"
59: readonly backing: Set<Fiber.Fiber<A, E>>
60: } | {
61: readonly _tag: "Closed"
62: }
63: }
64:
65: /**
66: * Checks whether a value is a FiberSet.
67: *
68: * **Example** (Checking if a value is a FiberSet)
69: *
70: * ```ts
71: * import { Effect, FiberSet } from "effect"
72: *
73: * Effect.gen(function*() {
74: * const set = yield* FiberSet.make()
75: *
76: * console.log(FiberSet.isFiberSet(set)) // true
77: * console.log(FiberSet.isFiberSet({})) // false
78: * })
79: * ```
80: *
81: * ...
Found 1 matches /home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/FiberSet.ts: Line 334: Deferred.doneUnsafe(self.deferred, exit as any)
<path>/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/FiberSet.ts</path>
<type>file</type>
<content>
285: * // The fiber is now managed by the set
286: * console.log(yield* FiberSet.size(set)) // 1
287: * })
288: * ```
289: *
290: * @category combinators
291: * @since 4.0.0
292: */
293: export const addUnsafe: {
294: <A, E, XE extends E, XA extends A>(
295: fiber: Fiber.Fiber<XA, XE>,
296: options?: {
297: readonly propagateInterruption?: boolean | undefined
298: } | undefined
299: ): (self: FiberSet<A, E>) => void
300: <A, E, XE extends E, XA extends A>(
301: self: FiberSet<A, E>,
302: fiber: Fiber.Fiber<XA, XE>,
303: options?: {
304: readonly propagateInterruption?: boolean | undefined
305: } | undefined
306: ): void
307: } = dual((args) => isFiberSet(args[0]), <A, E, XE extends E, XA extends A>(
308: self: FiberSet<A, E>,
309: fiber: Fiber.Fiber<XA, XE>,
310: options?: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
280:
281: const first = yield* session.prompt(input)
282: const second = yield* session.prompt(input)
283:
284: expect(second.id).not.toBe(first.id)
285: expect(yield* session.messages({ sessionID })).toEqual([])
286: expect(yield* admittedCount).toBe(2)
287: }),
288: )
289:
290: it.effect("returns the original recorded message when the ID is retried", () =>
291: Effect.gen(function* () {
292: yield* setup
293: const session = yield* SessionV2.Service
294: const input = {
295: sessionID,
296: id: messageID,
297: prompt: Prompt.make({ text: "Fix the failing tests" }),
298: resume: false,
299: }
300:
301: const first = yield* session.prompt(input)
302: const retried = yield* session.prompt(input)
303:
304: expect(retried).toEqual(first)
305: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1080: [sessionID, other].map((id) => ({
1081: id,
1082: project_id: Project.ID.global,
1083: slug: id,
1084: directory: "/project",
1085: title: "test",
1086: version: "test",
1087: })),
1088: )
1089: .run()
1090: yield* events.publish(SessionEvent.Step.Started, {
1091: sessionID: other,
1092: timestamp: created,
1093: assistantMessageID: assistantID,
1094: agent: "build",
1095: model,
1096: })
1097:
1098: const recovered = yield* events
1099: .publish(SessionEvent.Step.Recovered, {
1100: sessionID,
1101: timestamp: DateTime.makeUnsafe(1),
1102: executionID: "cross-session",
1103: assistantMessageID: assistantID,
1104: phase: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/recovery-id.ts</path>
<type>file</type>
<content>
1: import { SessionMessage } from "./message"
2:
3: export const message = (executionID: string, assistantMessageID?: SessionMessage.ID) =>
4: assistantMessageID ?? SessionMessage.ID.make(`msg_recovery_${executionID}`)
5:
6: export const continuation = (executionID: string) => SessionMessage.ID.make(`msg_recovery_continue_${executionID}`)
(End of file - total 6 lines)
</content>
Found 10 matches
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/input.ts:
Line 364: eq(SessionMessageTable.id, message(recovery.execution_id)),
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/message-updater.ts:
Line 266: id: message(event.data.executionID),
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/error.ts:
Line 9: override get message() {
Line 21: override get message() {
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/runner/model.ts:
Line 24: override get message() {
Line 36: override get message() {
Line 49: override get message() {
Line 62: override get message() {
/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/runner/publish-llm-event.ts:
Line 52: if (result.type === "error") return { error: { type: "unknown", message: message(result.value) } }
Line 54: if (!settled) throw new ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/database/migration.gen.ts</path>
<type>file</type>
<content>
1: import type { DatabaseMigration } from "./migration"
2:
3: export const migrations = (
4: await Promise.all([
5: import("./migration/20260127222353_familiar_lady_ursula"),
6: import("./migration/20260211171708_add_project_commands"),
7: import("./migration/20260213144116_wakeful_the_professor"),
8: import("./migration/20260225215848_workspace"),
9: import("./migration/20260227213759_add_session_workspace_id"),
10: import("./migration/20260228203230_blue_harpoon"),
11: import("./migration/20260303231226_add_workspace_fields"),
12: import("./migration/20260309230000_move_org_to_state"),
13: import("./migration/20260312043431_session_message_cursor"),
14: import("./migration/20260323234822_events"),
15: import("./migration/20260410174513_workspace-name"),
16: ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 140: \`message_id\` text NOT NULL, 141: \`session_id\` text NOT NULL, 142: \`time_created\` integer NOT NULL, 143: \`time_updated\` integer NOT NULL, 144: \`data\` text NOT NULL, 145: CONSTRAINT \`fk_part_message_id_message_id_fk\` FOREIGN KEY (\`message_id\`) REFERENCES \`message\`(\`id\`) ON DELETE CASCADE 146: ); 147: `) 148: yield* tx.run(` 149: CREATE TABLE \`session_context_epoch\` ( 150: \`session_id\` text PRIMARY KEY, 151: \`baseline\` text NOT NULL, 152: \`snapshot\` text NOT NULL, 153: \`baseline_seq\` integer NOT NULL, 154: CONSTRAINT \`fk_session_context_epoch_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE 155: ); 156: `) 157: yield* ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 235: \`tokens_cache_read\` integer DEFAULT 0 NOT NULL, 236: \`tokens_cache_write\` integer DEFAULT 0 NOT NULL, 237: \`revert\` text, 238: \`permission\` text, 239: \`agent\` text, 240: \`model\` text, 241: \`time_created\` integer NOT NULL, 242: \`time_updated\` integer NOT NULL, 243: \`time_compacting\` integer, 244: \`time_archived\` integer, 245: \`wake_attempts\` integer DEFAULT 0 NOT NULL, 246: CONSTRAINT \`fk_session_project_id_project_id_fk\` FOREIGN KEY (\`project_id\`) REFERENCES \`project\`(\`id\`) ON DELETE CASCADE 247: ); 248: `) 249: yield* tx.run(` 250: CREATE TABLE \`todo\` ( 251: \`session_id\` text NOT NULL, 252: \`content\` text NOT NULL, 253: \`status\` text NOT ...
The two prior blockers are closed in their stated cases: interruption keeps the claim through the real runner finalizer and cancellation cleanup, and provider reconciliation failures leave phase unknown. One separate fail-closed defect remains in the same settlement boundary: awaitToolFibers can return on the first failed tool fiber while sibling local tool fibers are still running, after which provider failure commits safe.
[
{
"content": "Verify immutable inputs, patch applicability, ancestry, and exact 33-file scope",
"status": "completed",
"priority": "high"
},
{
"content": "Trace execution claims, coordinator cleanup, interruption, provider failure, and tool reconciliation",
"status": "completed",
"priority": "high"
},
{
"content": "Trace recovery selection, promotion, replay, scheduling, fairness, and retry invariants",
"status": "completed",
"priority": "high"
},
{
"content": "Audit migration, generated artifacts, API layering, and all changed tests",
"status": "completed",
"priority": "high"
},
{
"content": "Reconcile findings against supplied validation and issue final merge verdict",
"status": "completed",
"priority": "high"
}
]
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/runner/index.ts</path>
<type>file</type>
<content>
1: export * as SessionRunner from "./index"
2:
3: import type { LLMError } from "@opencode-ai/llm"
4: import { Context, Effect } from "effect"
5: import { SessionSchema } from "../schema"
6: import type { SessionMessage } from "../message"
7: import type { ContextSnapshotDecodeError, MessageDecodeError } from "../error"
8: import { SessionRunnerModel } from "./model"
9: import type { SystemContext } from "../../system-context/index"
10: import type { ToolOutputStore } from "../../tool-output-store"
11:
12: export type RunError =
13: | LLMError
14: | SessionRunnerModel.Error
15: | MessageDecodeError
16: | ContextSnapshotDecodeError
17: | SystemContext.InitializationBlocked
18: | ToolOutputStore.Error
19:
20: /** Runs one local continuation from already-recorded Session history. ...
<path>/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
270:
271: yield* coordinator.wake("session")
272: yield* Deferred.await(firstStarted)
273: const interrupt = yield* coordinator.interrupt("session").pipe(Effect.forkChild)
274: yield* Deferred.await(cleanupStarted)
275: yield* coordinator.wake("session")
276: yield* Deferred.succeed(cleanupGate, undefined)
277: yield* Fiber.join(interrupt)
278: yield* Deferred.await(secondStarted)
279:
280: expect(runs).toBe(2)
281: }),
282: ),
283: )
284:
285: it.effect("finalizes interruption when cleanup dies", () =>
286: Effect.scoped(
287: Effect.gen(function* () {
288: const firstStarted = yield* Deferred.make<void>()
289: const cleanupStarted = yield* Deferred.make<void>()
290: const cleanupGate = yield* Deferred.make<void>()
291: ...
MUST-FIX
packages/core/src/session/runner/llm.ts:154-155 defines awaitToolFibers as raceFirst(FiberSet.join(...), FiberSet.awaitEmpty(...)). FiberSet.join fails on the first failed fiber; it does not wait for sibling fibers to finish.llm.ts:392, failUnsettledTools records failures at llm.ts:410-416, and phase becomes safe at llm.ts:417-420. The blocked local fiber remains alive until the scoped FiberSet finalizer runs at llm.ts:464.safe commit and scope finalization leaves an uncertain local side effect classified as safe. Recovery then clears the claim without admitting a continuation at packages/core/src/session/recovery.ts:132-137.packages/core/test/session-runner.test.ts:3566-3598 covers one successful blocked tool before provider failure. session-runner.test.ts:2060-2105 covers hosted-tool reconciliation failure. Neither combines an early failed local fiber with a still-running sibling.CONCERN
wake_attempts first at packages/core/src/session/recovery.ts:194-208; ordinary Sessions use the same order at recovery.ts:256-270. A continuous supply of at least 100 new rows with zero attempts can indefinitely outrank an older row with one attempt.packages/core/test/session-projector.test.ts:559-649 adds fresh prompts to already-attempted Sessions at lines 625-636. It does not add fresh competing Sessions, so it does not prove starvation freedom across fresh scheduler rows. ...Date: Sep 16, 2026 * 🔴 (13:28) User required that a newer turn supersede stale incomplete rows and that the system never resume an older assistant projection. * 🟡 (13:28) `/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/session/input.ts:498-531` defines `promoteSteers()`: selects unpromoted `delivery="steer"` inputs for `sessionID` with `admitted_seq <= cutoff`, excludes rows alr…
Date: Sep 16, 2026 * 🟡 (13:27) Investigation of `/tmp/opencode/session-recovery-review.1VQHG0/packages/core/src/tool/registry.ts` found that `ToolRegistry.materialize()` snapshots the currently active tool registrations and their object identities; its returned `settle()` calls `settleWith(input, registration.identity)`, and `settleWith()` returns `Stale tool call: ${input.call.name}` if the cur…
Date: Sep 16, 2026 * 🔴 (13:26) User asserted that session recovery must never commit continuation before unresolved hosted tools are reconciled; coverage is in `/tmp/opencode/session-recovery-review.1VQHG0/packages/core/test/session-runner.test.ts:1963`, with a simulated database abort `"crash before hosted tool reconciliation"` at line 1988. * 🔴 (13:26) User asserted that session recovery must…
Date: Sep 16, 2026 * 🟡 (13:26) Investigation of Effect’s fiber-set semantics found `FiberSet.join()` in `/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/FiberSet.ts:680-681` awaits `self.deferred` and terminates with the first fiber failure, whereas `FiberSet.awaitEmpty()` at lines 708-712 waits until the set is empty without propagating fiber failures.
Date: Sep 16, 2026 * 🟡 (13:22) Tool reported no files found. * 🟡 (13:22) Patch artifact `/tmp/opencode/session-recovery-final-v6.patch` has SHA-256 `a5c1bace440d1b6acb383c4fcffc52714a9373892e75b4f23bdbbf2dfdad8e16`. * 🟡 (13:23) Built SEA binary `/home/byk/Code/opencode/packages/opencode/dist/server/opencode-server` has SHA-256 `d09c992a0f4cce15e73dd158fe60413420109c821afed6e121d11cdad9c7b583`.…