DashboardopencodeSession 1Tc0aHgT03zU

Session 1Tc0aHgT03zU

Full ID: 1Tc0aHgT03zUwjNb2
Messages: 45
Distillations: 0
Time range: 2026-09-16 00:32:28 — 2026-09-16 00:59:02

Conversation (45 messages)

Perform an independent, read-only adversarial correctness review of the exact frozen interrupted-session recovery candidate.

Immutable scope:

  • Base commit: ab9408c81cc9916177641ae8e9f08624fc27b5b9
  • Current committed HEAD identity: dc661f30cb2055ad6248fdc59b14998d834497be (the canonical patch includes package working-tree changes beyond it)
  • Canonical complete patch: /tmp/opencode/session-recovery-final-v3.patch
  • Required patch SHA-256: b4b30666f3a5e9a5bcbfe7237b16e113f684568d67b9d360e65c5655e649009f
  • Exact SEA artifact: /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server
  • Required SEA SHA-256: 2dd8f7fffa4b382799ebfa093932baa6dd7c5f3e896de5553edb14dbccd7b627
  • The patch applies cleanly to the immutable base and covers exactly 32 package files including untracked execution/claim.ts and recovery-id.ts.

Do not edit files, run formatters, start services, or mutate VCS. Read the patch and immutable base/current blobs as needed. First verify both hashes exactly. Review every patch file, with special focus on:

  • safe restart policy: never replay uncertain provider/tool work; synthetic queued continue only after model-visible reconciliation
  • no SessionPrompt and no Core -> Server dependency
  • exact lease fencing, expiry, heartbeat, stale-owner revival, takeover, external-dispatch boundaries
  • pre-promotion crash windows and the new atomic admission/recovery boundary
  • recovery input reservation across public retries, durable replay/projectors, ordinary redrive, bulk steer, queue, exact promotion, malformed/quarantined rows
  • newest-assistant supersession
  • continuation/compaction durability
  • cancellation/interruption ordering and successor wakes
  • recovery outbox replay/idempotence and lost advisory wakes
  • deterministic bounded batches and poison progress
  • migration and generated schema/API/SDK consistency
  • tests: whether regressions exercise real preconditions and would fail if each guard were removed

Known validation evidence (do not trust ...

gateway · 2026-09-16 00:32:28
result
b4b30666f3a5e9a5bcbfe7237b16e113f684568d67b9d360e65c5655e649009f  /tmp/opencode/session-recovery-final-v3.patch
2dd8f7fffa4b382799ebfa093932baa6dd7c5f3e896de5553edb14dbccd7b627  /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server
dc661f30cb2055ad6248fdc59b14998d834497be
gateway · 2026-09-16 00:33:03
result
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/core/schema.json b/packages/core/schema.json
2: index d0eeeebd5c419b02287413e8e521fefcbc3c4f27..dad07b6ce6bdb941c0bde9bc7224749dedfc6a39 100644
3: --- a/packages/core/schema.json
4: +++ b/packages/core/schema.json
5: @@ -1,8 +1,8 @@
6:  {
7:    "version": "7",
8:    "dialect": "sqlite",
9: -  "id": "f14a9b18-8207-487e-a3d3-227e629ba9ad",
10: -  "prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
11: +  "id": "797e6838-8a41-4793-932a-d200aa8d51fb",
12: +  "prevIds": ["0445bfe8-162e-4719-a49b-23f1210dcacd"],
13:    "ddl": [
14:      {
15:        "name": "workspace",
16: @@ -60,6 +60,10 @@
17:        "name": "session_context_epoch",
18:        "entityType": "tables"
19:      },
20: +    {
21: +      "name": "session_execution",
22: +      "entityType": "tables"
23: +    },
24:      {
25:        "name": "session_input",
26:        "entityType": "tables"
27: @@ -68,6 +72,10 ...
gateway · 2026-09-16 00:33:18
result
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
1367: @@ -247,42 +409,84 @@ export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
1368:    events: EventV2.Interface,
1369:    sessionID: SessionSchema.ID,
1370:    cutoff: number,
1371: +  commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
1372:  ) {
1373:    const rows = yield* db
1374: -    .select()
1375: +    .select({ input: SessionInputTable })
1376:      .from(SessionInputTable)
1377: +    .leftJoin(
1378: +      SessionRecoveryTable,
1379: +      eq(SessionRecoveryTable.continuation_message_id, SessionInputTable.id),
1380: +    )
1381:      .where(
1382:        and(
1383:          eq(SessionInputTable.session_id, sessionID),
1384:          isNull(SessionInputTable.promoted_seq),
1385:          eq(SessionInputTable.delivery, "steer"),
1386:          lte(SessionInputTable.admitted_seq, cutoff),
1387: +        ...
gateway · 2026-09-16 00:33:27
result
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
2533: +      const hasQueue = recovering || hasSteer ? false : yield* SessionInput.hasPending(db, input.sessionID, "queue")
2534: +      if (!recovering && !input.force && !hasSteer && !hasQueue) return
2535: +      yield* failInterruptedTools(input.sessionID, claim)
2536: +      let promotion: Promotion | undefined = input.recoveryInputID
2537: +        ? { recoveryInputID: input.recoveryInputID }
2538: +        : hasSteer
2539: +          ? "steer"
2540: +          : hasQueue
2541: +            ? "queue"
2542: +            : undefined
2543: +      let shouldRun = recovering || input.force || hasSteer || hasQueue
2544:        while (shouldRun) {
2545:          let needsContinuation = true
2546:          let step = 1
2547:          while (needsContinuation) {
2548: -          const result = yield* runTurn(input.sessionID, promotion, step)
2549: +          const result = yield* ...
gateway · 2026-09-16 00:33:32
result
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
3782: +            directory: "/project",
3783: +            title: "test",
3784: +            version: "test",
3785: +          })),
3786: +        )
3787: +        .run()
3788: +      yield* events.publish(SessionEvent.Step.Recovered, {
3789: +        sessionID,
3790: +        timestamp: created,
3791: +        executionID: "cross-session-continuation",
3792: +        phase: "ready",
3793: +      })
3794: +      yield* db.run(
3795: +        sql`UPDATE session_input SET session_id = ${other} WHERE id = ${continuation("cross-session-continuation")}`,
3796: +      )
3797: +      const wakes: string[] = []
3798: +
3799: +      yield* SessionRecovery.recover().pipe(
3800: +        Effect.provideService(
3801: +          SessionExecution.Service,
3802: +          SessionExecution.Service.of({
3803: +            ...recoveryExecution,
3804: +            wake: (id) => Effect.sync(() => ...
gateway · 2026-09-16 00:33:41
result
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
5187:              ? { type: "tool", id: fragmentID(kind, "interrupted"), state: { status: "error" } }
5188: @@ -555,6 +595,289 @@ const verifyPartialFlushOnInterruption = (kind: FragmentKind) =>
5189:    })
5190:  
5191:  describe("SessionRunnerLLM", () => {
5192: +  it.effect("redrives only the synthetic recovery continuation", () =>
5193: +    Effect.gen(function* () {
5194: +      yield* setup
5195: +      const session = yield* SessionV2.Service
5196: +      const events = yield* EventV2.Service
5197: +      const executionService = yield* SessionExecution.Service
5198: +      const unrelatedID = SessionMessage.ID.make("msg_recovery_unrelated")
5199: +      yield* session.prompt({
5200: +        id: unrelatedID,
5201: +        sessionID,
5202: +        prompt: Prompt.make({ text: "unrelated admit-only" }),
5203: +        delivery: "queue",
5204: +        resume: false,
5205: +      })
5206: +   ...
gateway · 2026-09-16 00:33:48
result
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
6633:                    },
6634: -                  "reasoningID": {
6635: +                  "textID": {
6636:                      "type": "string"
6637:                    },
6638:                    "text": {
6639:                      "type": "string"
6640: -                  },
6641: -                  "providerMetadata": {
6642: -                    "$ref": "#/components/schemas/LLMProviderMetadata"
6643:                    }
6644:                  },
6645: -                "required": ["timestamp", "sessionID", "assistantMessageID", "reasoningID", "text"],
6646: +                "required": ["timestamp", "sessionID", "assistantMessageID", "textID", "text"],
6647:                  "additionalProperties": false
6648:                }
6649:              },
6650: @@ -26258,7 +26458,7 @@
6651:          "required": ["type", "id", "syncEvent"],
6652:          "additionalProperties": false
6653:    ...
gateway · 2026-09-16 00:33:55
result
Found 32 matches
/tmp/opencode/session-recovery-final-v3.patch:
  Line 1: diff --git a/packages/core/schema.json b/packages/core/schema.json

  Line 410: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts

  Line 421: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts

  Line 477: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts

  Line 563: diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts

  Line 595: diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts

  Line 684: diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts

  Line 860: diff --git a/packages/core/src/session/execution/local.ts b/packages/core/src/session/execution/local.ts

  Line 1029: diff --git ...
gateway · 2026-09-16 00:34:39
result
<skill_content name="effect">
# Skill: effect

# Effect

This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows.

## Source Of Truth

Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples.

1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder.
2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code.
3. Also inspect existing repo code for local house style before introducing new patterns.
4. Prefer answers and implementations backed by specific source files or nearby repo examples.

## Guidelines

- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses.
- Use `Effect.gen(function* () { ... })` for multi-step workflows.
- Use `Effect.fn("Name")` or ...
gateway · 2026-09-16 00:36:18
result
<path>/home/byk/Code/opencode/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2: 
3: import { and, asc, desc, eq, 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 { continuation, message } from "./recovery-id"
13: import { Prompt } from "./prompt"
14: import { SessionInput } from "./input"
15: import { SessionSchema } from "./schema"
16: import {
17:   SESSION_EXECUTION_LEASE_MS,
18:   SessionExecutionTable,
19:   SessionInputTable,
20:   SessionRecoveryTable,
21:   ...
gateway · 2026-09-16 00:37:27
result
<path>/home/byk/Code/opencode/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. ...
gateway · 2026-09-16 00:38:03

Your prior response was empty and therefore invalid. This is your one permitted evidence-or-blocker retry. The candidate remains unchanged and the hashes were just reverified:

  • /tmp/opencode/session-recovery-final-v3.patch SHA-256 b4b30666f3a5e9a5bcbfe7237b16e113f684568d67b9d360e65c5655e649009f
  • /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server SHA-256 2dd8f7fffa4b382799ebfa093932baa6dd7c5f3e896de5553edb14dbccd7b627
  • base ab9408c81cc9916177641ae8e9f08624fc27b5b9

Complete the original independent read-only correctness review now. Do not use Lore recall. Read the canonical patch directly and inspect base blobs or current package files only when needed. Do not edit, test, build, start services, or mutate VCS. You must return substantive evidence: either findings with exact patch/current file:line references or an exact blocker naming the failed tool/path. Classify MUST-FIX, CONCERN, and PASS; explicitly assess closure of prior v2 findings; end exactly MERGE or DO-NOT-MERGE. Empty or progress-only output disqualifies this reviewer.

gateway · 2026-09-16 00:41:02
result
...output truncated...

Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0a7a8f22c001TF1P1I3OV2AZYU

+        { promoted_seq: null },
+      )
+      expect(yield* db.select().from(SessionInputTable).where(eq(SessionInputTable.id, ordinaryID)).get()).toMatchObject({
+        promoted_seq: expect.any(Number),
+      })
+    }),
+  )
+
+  it.effect("quarantines a malformed claim without blocking a valid claim", () =>
+    Effect.gen(function* () {
+      const { db } = yield* Database.Service
+      const validSession = SessionV2.ID.make("ses_recovery_valid")
+      yield* db
+        .insert(ProjectTable)
+        .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
+        .run()
+      yield* db
+        .insert(SessionTable)
+        .values(
+          [sessionID, validSession].map((id) => ({
+            id,
+            project_id: Project.ID.global,
+            slug: id,
+            directory: "/project",
+           ...
gateway · 2026-09-16 00:41:15
result
No files found
gateway · 2026-09-16 00:41:49
result
/home/byk/Code/opencode/packages/core/src/event.ts
gateway · 2026-09-16 00:42:04
result
Found 34 matches
/home/byk/Code/opencode/packages/core/src/event.ts:
  Line 122:   /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */

  Line 123:   readonly commit?: (seq: number) => Effect.Effect<void>

  Line 127:   readonly publish: <D extends Definition>(

  Line 140:     options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },

  Line 144:     options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },

  Line 205:       function commitDurableEvent(

  Line 214:         commit?: (seq: number) => Effect.Effect<void>,

  Line 239:                   const committed = yield* db

  Line 316:                           const committed = {

  Line 321:                             yield* projector(committed)

  Line 323:                           if (commit) yield* commit(seq)

  Line 354:                   if (committed) {

  Line 356:          ...
gateway · 2026-09-16 00:42:23
result
<path>/home/byk/Code/opencode/packages/core/src/event.ts</path>
<type>file</type>
<content>
110: export class SubscriberOverflowError extends Schema.TaggedErrorClass<SubscriberOverflowError>()(
111:   "EventV2.SubscriberOverflow",
112:   { capacity: Schema.Int },
113: ) {}
114: 
115: export const define = Event.define
116: export const versionedType = Event.versionedType
117: 
118: export interface PublishOptions {
119:   readonly id?: ID
120:   readonly metadata?: Record<string, unknown>
121:   readonly location?: Location.Ref
122:   /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
123:   readonly commit?: (seq: number) => Effect.Effect<void>
124: }
125: 
126: export interface Interface {
127:   readonly publish: <D extends Definition>(
128:     definition: D,
129:     data: Data<D>,
130:     options?: PublishOptions,
131:   ) => Effect.Effect<Payload<D>>
132:   readonly subscribe: <D extends Definition>(definition: D) => ...
gateway · 2026-09-16 00:43:33
result
Found 7 matches
/tmp/opencode/session-recovery-final-v3.patch:
  Line 3236:  import { Prompt } from "@opencode-ai/core/session/prompt"

  Line 5023:  import { Prompt } from "@opencode-ai/core/session/prompt"

  Line 5072:  import { Prompt } from "@opencode-ai/core/session/prompt"

  Line 5747:  import { CorsConfig, isAllowedCorsOrigin, type CorsOptions } from "@opencode-ai/server/cors"

  Line 7213: diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts

  Line 7215: --- a/packages/server/src/routes.ts

  Line 7216: +++ b/packages/server/src/routes.ts
gateway · 2026-09-16 00:43:51
result
<path>/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
170:       }
171:     }
172: 
173:     const continueAfterCompaction = (step: number) => new TurnTransitionError({ _tag: "ContinueAfterCompaction", step })
174:     const continueAfterOverflowCompaction = (step: number) =>
175:       new TurnTransitionError({ _tag: "ContinueAfterOverflowCompaction", step })
176: 
177:     const loadSystemContext = (agent: AgentV2.Selection) =>
178:       Effect.all([systemContext.load(), skillGuidance.load(agent), referenceGuidance.load()], {
179:         concurrency: "unbounded",
180:       }).pipe(Effect.map(SystemContext.combine))
181: 
182:     const runTurnAttempt = Effect.fn("SessionRunner.runTurn")(function* (
183:       sessionID: SessionSchema.ID,
184:       executionID: string,
185:       promotion: Promotion | undefined,
186:       step: number,
187:       ownerID: string,
188:       recoverOverflow?: typeof ...
gateway · 2026-09-16 00:44:13
result
<path>/home/byk/Code/opencode/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: type Entry<E> = {
18: ...
gateway · 2026-09-16 00:45:04
result
<path>/home/byk/Code/opencode/packages/schema/src/event-manifest.ts</path>
<type>file</type>
<content>
1: export * as EventManifest from "./event-manifest"
2: 
3: import { Catalog } from "./catalog"
4: import { Durable } from "./durable-event-manifest"
5: import { Event } from "./event"
6: import { FileSystem } from "./filesystem"
7: import { FileSystemWatcher } from "./filesystem-watcher"
8: import { InstallationEvent } from "./installation-event"
9: import { Integration } from "./integration"
10: import { LegacyEvent } from "./legacy-event"
11: import { LspEvent } from "./lsp-event"
12: import { McpEvent } from "./mcp-event"
13: import { ModelsDev } from "./models-dev"
14: import { Permission } from "./permission"
15: import { PermissionV1 } from "./permission-v1"
16: import { Plugin } from "./plugin"
17: import { Project } from "./project"
18: import { ProjectDirectories } from "./project-directories"
19: import { Pty } from "./pty"
20: import { Question } from "./question"
21: ...
gateway · 2026-09-16 00:45:19
result
<path>/home/byk/Code/opencode/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 Committed = ...
gateway · 2026-09-16 00:46:02
result
<path>/home/byk/Code/opencode/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>
gateway · 2026-09-16 00:46:33
result
Found 42 matches
/home/byk/Code/opencode/packages/schema/test/event-manifest.test.ts:
  Line 53:       SessionEvent.PromptExecutionRequested,


/home/byk/Code/opencode/packages/schema/src/session-event.ts:
  Line 104: export const PromptExecutionRequested = Event.define({

  Line 112: export type PromptExecutionRequested = typeof PromptExecutionRequested.Type

  Line 221:     type: "session.next.step.recovered",

  Line 490:   PromptExecutionRequested,

  Line 524:   PromptExecutionRequested,


/home/byk/Code/opencode/packages/sdk/openapi.json:
  Line 15339:             "$ref": "#/components/schemas/EventSessionNextStepRecovered"

  Line 18289:                     "enum": ["session.next.step.recovered"]

  Line 20607:                 "$ref": "#/components/schemas/SyncEventSessionNextStepRecovered"

  Line 23841:             "$ref": "#/components/schemas/SessionNextStepRecovered"

  Line 24230:             "$ref": "#/components/schemas/SessionNextStepRecovered"

  Line 26283:       ...
gateway · 2026-09-16 00:46:49
result
<path>/home/byk/Code/opencode/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
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 { 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, SessionRecoveryTable } 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: export const decode = (value: unknown) => Schema.decodeUnknownOption(Prompt)(value)
22: 
23: ...
gateway · 2026-09-16 00:46:59
result
<path>/home/byk/Code/opencode/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
470: export const promoteExact = Effect.fn("SessionInput.promoteExact")(function* (
471:   db: DatabaseService,
472:   events: EventV2.Interface,
473:   sessionID: SessionSchema.ID,
474:   messageID: SessionMessage.ID,
475:   commit: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
476: ) {
477:   const row = yield* db
478:     .select()
479:     .from(SessionInputTable)
480:     .where(
481:       and(
482:         eq(SessionInputTable.id, messageID),
483:         eq(SessionInputTable.session_id, sessionID),
484:         isNull(SessionInputTable.promoted_seq),
485:         eq(SessionInputTable.delivery, "queue"),
486:       ),
487:     )
488:     .get()
489:     .pipe(Effect.orDie)
490:   if (!row) return yield* Effect.die(`Pending recovery input not found: ${messageID}`)
491:   yield* publish(db, events, sessionID, [row], commit)
492: })

(End of file - total 492 ...
gateway · 2026-09-16 00:47:38
result
<path>/home/byk/Code/opencode/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: number) =>
26:   and(
27:  ...
gateway · 2026-09-16 00:47:55
result
Found 38 matches
/home/byk/Code/opencode/packages/core/src/snapshot.ts:
  Line 19:   operation: Schema.Literals(["capture", "files", "diff", "preview", "restore"]),

  Line 47:    * best-effort capture fails.

  Line 49:   readonly capture: () => Effect.Effect<ID | undefined>

  Line 52:    * List project-relative paths changed between two captured trees without

  Line 58:    * Generate structured per-file diffs between two captured trees. `context`

  Line 77:    * Replace the snapshot index with a captured tree and check out all its entries.

  Line 103:         return yield* new Error({ operation: "capture", message: "Location is outside the project" })

  Line 108:       if (!source) return yield* new Error({ operation: "capture", message: "Project is not a Git repository" })

  Line 121:         .pipe(Effect.mapError((cause) => failure("capture", cause)))

  Line 129:     const capture = Effect.fn("Snapshot.capture")(function* () {

  Line 134:           yield* ...
gateway · 2026-09-16 00:48:10
result
<path>/home/byk/Code/opencode/packages/core/src/snapshot.ts</path>
<type>file</type>
<content>
90:     const fs = yield* FSUtil.Service
91:     const git = yield* Git.Service
92:     const global = yield* Global.Service
93:     const location = yield* Location.Service
94:     const source = yield* git.repo.discover(location.project.directory)
95:     const worktree = source
96:       ? AbsolutePath.make(yield* fs.realPath(source.worktree).pipe(Effect.orDie))
97:       : location.project.directory
98:     const gitDirectory = AbsolutePath.make(path.join(global.data, "snapshot", location.project.id, Hash.fast(worktree)))
99: 
100:     const scope = Effect.fnUntraced(function* () {
101:       const relative = path.relative(worktree, location.directory)
102:       if (relative.startsWith("..") || path.isAbsolute(relative))
103:         return yield* new Error({ operation: "capture", message: "Location is outside the project" })
104:       return RelativePath.make(relative.replaceAll("\\", ...
gateway · 2026-09-16 00:49:05
result
<path>/home/byk/Code/opencode/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
760:       expect(yield* db.select().from(SessionRecoveryTable).all()).toHaveLength(1)
761:       expect(yield* db.select().from(SessionInputTable).all()).toHaveLength(1)
762:     }),
763:   )
764: 
765:   it.effect("never recovers a pre-promotion claim when input commits at the recovery boundary", () =>
766:     Effect.gen(function* () {
767:       const { db } = yield* Database.Service
768:       const events = yield* EventV2.Service
769:       yield* db
770:         .insert(ProjectTable)
771:         .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
772:         .run()
773:       yield* db
774:         .insert(SessionTable)
775:         .values({
776:           id: sessionID,
777:           project_id: Project.ID.global,
778:           slug: "test",
779:           directory: "/project",
780:           title: "test",
781:           ...
gateway · 2026-09-16 00:49:58
result
Found 1 matches
/home/byk/Code/opencode/packages/core/src/session/projector.ts:
  Line 504:           .delete(SessionMessageTable)
gateway · 2026-09-16 00:50:22
result
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
470:     yield* events.project(SessionEvent.RevertEvent.Staged, (event) =>
471:       db
472:         .update(SessionTable)
473:         .set({
474:           revert: { ...event.data.revert, files: event.data.revert.files ? [...event.data.revert.files] : undefined },
475:           time_updated: DateTime.toEpochMillis(event.data.timestamp),
476:         })
477:         .where(eq(SessionTable.id, event.data.sessionID))
478:         .run()
479:         .pipe(Effect.orDie, Effect.asVoid),
480:     )
481:     yield* events.project(SessionEvent.RevertEvent.Cleared, (event) =>
482:       db
483:         .update(SessionTable)
484:         .set({ revert: null, time_updated: DateTime.toEpochMillis(event.data.timestamp) })
485:         .where(eq(SessionTable.id, event.data.sessionID))
486:         .run()
487:         .pipe(Effect.orDie, Effect.asVoid),
488:     )
489:     yield* ...
gateway · 2026-09-16 00:50:53
result
Found 5 matches
/home/byk/Code/opencode/packages/core/src/session/runner/publish-llm-event.ts:
  Line 84:   let stepSettlement: { readonly finish: string; readonly tokens: ReturnType<typeof tokens> } | undefined

  Line 444:         if (stepSettlement) return yield* Effect.die("Duplicate step finish")

  Line 445:         stepSettlement = { finish: event.reason, tokens: tokens(event.usage) }

  Line 464:     hasProviderError: () => providerFailed,

  Line 465:     stepSettlement: () => stepSettlement,
gateway · 2026-09-16 00:51:07
result
<path>/home/byk/Code/opencode/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
360:         const tool = tools.get(event.id)!
361:         if (!tool.inputEnded) yield* endToolInput(event)
362:         if (tool.name !== event.name)
363:           return yield* Effect.die(`Tool call name changed for ${event.id}: ${tool.name} -> ${event.name}`)
364:         if (tool.called) return yield* Effect.die(`Duplicate tool call: ${event.id}`)
365:         tool.called = true
366:         tool.providerExecuted = event.providerExecuted === true
367:         tool.providerMetadata = event.providerMetadata
368:         yield* publishEvent(SessionEvent.Tool.Called, {
369:           sessionID: input.sessionID,
370:           timestamp: yield* timestamp,
371:           assistantMessageID: tool.assistantMessageID,
372:           callID: event.id,
373:           tool: event.name,
374:           input: record(event.input),
375:           provider: {
376:             ...
gateway · 2026-09-16 00:51:26
result
<path>/home/byk/Code/opencode/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
190:           const currentAssistant = yield* adapter.getCurrentAssistant()
191:           if (currentAssistant) {
192:             yield* adapter.updateAssistant(
193:               produce(currentAssistant, (draft) => {
194:                 draft.time.completed = event.data.timestamp
195:               }),
196:             )
197:           }
198:           yield* adapter.appendMessage(
199:             SessionMessage.Assistant.make({
200:               id: event.data.assistantMessageID,
201:               type: "assistant",
202:               agent: event.data.agent,
203:               model: event.data.model,
204:               time: { created: event.data.timestamp },
205:               content: [],
206:               snapshot: event.data.snapshot ? { start: event.data.snapshot } : undefined,
207:             }),
208:           )
209:         })
210:       },
211:       ...
gateway · 2026-09-16 00:52:24
result
Found 42 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
  Line 1816:         LLMEvent.toolCall({ id: "call-error", name: "write", input: { path: "README.md" }, providerExecuted: true }),

  Line 1823:           providerExecuted: true,

  Line 1836:           providerExecuted: true,

  Line 2131:           id: "hosted-search",

  Line 2134:           providerExecuted: true,

  Line 2135:           providerMetadata: { openai: { itemId: "hosted-search" } },

  Line 2138:           id: "hosted-search",

  Line 2141:           providerExecuted: true,

  Line 2158:           id: "hosted-search",

  Line 2161:           providerExecuted: true,

  Line 2162:           providerMetadata: { openai: { itemId: "hosted-search" } },

  Line 2166:           id: "hosted-search",

  Line 2169:           providerExecuted: true,

  Line 2811:   it.effect("durably fails hosted tools left running by a prior process before continuing inline", () =>

  Line 2818:         prompt: ...
gateway · 2026-09-16 00:53:04
result
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3760:       yield* setup
3761:       const session = yield* SessionV2.Service
3762:       yield* session.prompt({
3763:         sessionID,
3764:         prompt: Prompt.make({ text: "Do not continue failed provider" }),
3765:         resume: false,
3766:       })
3767: 
3768:       requests.length = 0
3769:       const executionCount = executions.length
3770:       response = [
3771:         LLMEvent.stepStart({ index: 0 }),
3772:         LLMEvent.toolCall({ id: "call-before-provider-error", name: "echo", input: { text: "settled" } }),
3773:         LLMEvent.providerError({ message: "Provider unavailable" }),
3774:       ]
3775: 
3776:       yield* session.resume(sessionID)
3777: 
3778:       expect(requests).toHaveLength(1)
3779:       expect(executions.slice(executionCount)).toEqual(["settled"])
3780:     }),
3781:   )
3782: 
3783:   it.effect("durably fails a hosted tool when ...
gateway · 2026-09-16 00:54:06
result
(no output)
gateway · 2026-09-16 00:55:02
result
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
370:     )
371:     yield* events.project(SessionEvent.PromptAdmitted, (event) =>
372:       Effect.gen(function* () {
373:         if (event.durable === undefined) return yield* Effect.die("Durable Session event is missing aggregate sequence")
374:         yield* SessionInput.projectAdmitted(db, {
375:           admittedSeq: event.durable.seq,
376:           id: event.data.messageID,
377:           sessionID: event.data.sessionID,
378:           prompt: event.data.prompt,
379:           delivery: event.data.delivery,
380:           requestExecution: event.data.requestExecution ?? false,
381:           timeCreated: event.data.timestamp,
382:         })
383:       }),
384:     )
385:     yield* events.project(SessionEvent.PromptExecutionRequested, (event) =>
386:       SessionInput.projectExecutionRequested(db, {
387:         id: event.data.messageID,
388:         sessionID: ...
gateway · 2026-09-16 00:56:21
result
Found 5 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
  Line 598:   it.effect("redrives only the synthetic recovery continuation", () =>

  Line 784:   it.effect("never starts a local tool after ownership changes", () =>

  Line 1474:   it.effect("keeps automatic compaction unknown until Compaction.Ended commits", () =>

  Line 1954:   it.effect("recovers the continuation committed with a settled tool step", () =>


/home/byk/Code/opencode/packages/core/test/session-projector.test.ts:
  Line 494:   it.effect("quarantines a full batch of poison recovery rows before redriving valid work", () =>
gateway · 2026-09-16 00:56:42
result
<path>/home/byk/Code/opencode/packages/core/src/session.ts</path>
<type>file</type>
<content>
350:             .pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))),
351:         ).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))),
352:       history: Effect.fn("V2Session.history")(function* (input) {
353:         yield* result.get(input.sessionID)
354:         return yield* EventV2.readAggregate(db, {
355:           ...input,
356:           aggregateID: input.sessionID,
357:           manifest: SessionDurable,
358:         })
359:       }),
360:       prompt: Effect.fn("V2Session.prompt")((input) =>
361:         Effect.uninterruptible(
362:           Effect.gen(function* () {
363:             yield* result.get(input.sessionID)
364:             const prompt = resolvePrompt(input.prompt)
365:             const messageID = input.id ?? SessionMessage.ID.create()
366:             const delivery = ...
gateway · 2026-09-16 00:57:10
result
<path>/home/byk/Code/opencode/packages/core/src/session/compaction.ts</path>
<type>file</type>
<content>
180: }
181: 
182: export const make = (dependencies: Dependencies) => {
183:   const config = settings(dependencies.config)
184:   const compactAfterOverflow = Effect.fn("SessionCompaction.compactAfterOverflow")(function* (input: CompactInput) {
185:     const context = input.model.route.defaults.limits?.context
186:     if (context === undefined || context <= 0) return false
187:     const output = input.request.generation?.maxTokens ?? input.model.route.defaults.limits?.output ?? 0
188:     const selected = select(input.entries, config.tokens)
189:     const previousSummary = input.entries.find((entry) => entry.message.type === "compaction")?.message
190:     if (!selected || (selected.head.length === 0 && previousSummary?.type !== "compaction")) return false
191:     const summaryPrompt = buildPrompt({
192:       previousSummary: previousSummary?.type === "compaction" ? ...
gateway · 2026-09-16 00:57:33
result
<path>/home/byk/Code/opencode/packages/core/src/session/sql.ts</path>
<type>file</type>
<content>
145:     session_id: text()
146:       .$type<SessionSchema.ID>()
147:       .notNull()
148:       .references(() => SessionTable.id, { onDelete: "cascade" }),
149:     prompt: text({ mode: "json" }).notNull().$type<Prompt>(),
150:     delivery: text().$type<SessionInput.Delivery>().notNull(),
151:     admitted_seq: integer().notNull(),
152:     promoted_seq: integer(),
153:     wake_pending: integer({ mode: "boolean" }).notNull().default(false),
154:     time_created: integer()
155:       .notNull()
156:       .$default(() => Date.now()),
157:   },
158:   (table) => [
159:     index("session_input_session_pending_delivery_seq_idx").on(
160:       table.session_id,
161:       table.promoted_seq,
162:       table.delivery,
163:       table.admitted_seq,
164:     ),
165:     uniqueIndex("session_input_session_admitted_seq_idx").on(table.session_id, table.admitted_seq),
166:     ...
gateway · 2026-09-16 00:59:02

Frozen identity

  • Patch SHA-256 verified: b4b30666f3a5e9a5bcbfe7237b16e113f684568d67b9d360e65c5655e649009f
  • SEA SHA-256 verified: 2dd8f7fffa4b382799ebfa093932baa6dd7c5f3e896de5553edb14dbccd7b627
  • Base and HEAD commit objects verified.
  • The canonical patch contains exactly 32 files.
  • git apply --check --reverse /tmp/opencode/session-recovery-final-v3.patch succeeds against the reviewed package files.
  • I did not edit files, run tests or builds, start services, or mutate VCS.

MUST-FIX

1. A recovery continuation can bypass validation and quarantine

SessionExecutionLocal selects any pending recovery row and passes its continuation directly to the runner:

  • packages/core/src/session/execution/local.ts:48-63

That query checks only session_id, wake_pending, and recovery_error. It does not validate:

  • deterministic continuation identity
  • recovery phase
  • continuation prompt
  • assistant relationship
  • newest-assistant supersession
  • required synthetic reconciliation message

Those checks exist only in the periodic scanner:

  • packages/core/src/session/recovery.ts:198-310

The runner then calls promoteExact:

  • packages/core/src/session/runner/llm.ts:218-225

But promoteExact checks only ID, Session, pending status, and queue delivery:

  • packages/core/src/session/input.ts:470-491

Concrete failure sequence:

  1. A persisted recovery row is stale because a newer assistant exists, but the scanner has not quarantined it yet.
  2. An explicit resume, ordinary prompt wake, or concurrent startup wake starts a local drain.
  3. The local drain selects the unvalidated recovery row.
  4. promoteExact promotes it and clears its wake through the Prompted projector.
  5. The provider runs the stale synthetic continuation despite newest-assistant supersession. ...
gpt-5.6-sol · 2026-09-16 00:59:02 · ~1.8K tokens