fix(core): harden vcs backend lifecycle

This commit is contained in:
Shoubhit Dash 2026-07-06 18:20:17 +05:30
commit 10a71bae4a
2 changed files with 21 additions and 4 deletions

View file

@ -3,7 +3,7 @@ export * as VcsBackends from "./backends"
import { Vcs } from "@opencode-ai/plugin/v2/effect"
import { FileDiff } from "@opencode-ai/schema/file-diff"
import { FileStatus } from "@opencode-ai/schema/vcs"
import { Context, Effect, Exit, Layer, Option, Schema } from "effect"
import { Cause, Context, Effect, Exit, Layer, Option, Schema } from "effect"
import type { Scope } from "effect"
import { ConfigVcs } from "../config/vcs"
import { makeLocationNode } from "../effect/app-node"
@ -46,8 +46,12 @@ const layer = Layer.effect(
message: `Vcs backend '${backend.type}' is already registered`,
})
}
registry.set(backend.type, { backend })
yield* Effect.addFinalizer(() => Effect.sync(() => registry.delete(backend.type)))
yield* Effect.uninterruptible(
Effect.gen(function* () {
registry.set(backend.type, { backend })
yield* Effect.addFinalizer(() => Effect.sync(() => registry.delete(backend.type)))
}),
)
}),
get: (type) => {
const vcs = location.vcsBackend
@ -87,6 +91,7 @@ function sanitize<A>(type: string, operation: string, decode: (input: unknown) =
Effect.exit,
Effect.flatMap((exit) => {
if (Exit.isFailure(exit)) {
if (Cause.hasInterrupts(exit.cause)) return Effect.failCause(exit.cause)
return Effect.logWarning("vcs backend failed", { type, operation, cause: exit.cause }).pipe(
Effect.as([] as readonly A[]),
)

View file

@ -1,5 +1,5 @@
import { describe, expect } from "bun:test"
import { Effect, Exit, Layer, Scope } from "effect"
import { Cause, Effect, Exit, Fiber, Layer, Scope } from "effect"
import { Vcs as PluginVcs } from "@opencode-ai/plugin/v2/effect"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { Location } from "@opencode-ai/core/location"
@ -116,6 +116,18 @@ describe("VcsBackends", () => {
}).pipe(Effect.scoped, provide),
)
it.live("preserves adapter interruption", () =>
Effect.gen(function* () {
yield* register(backend({ status: () => Effect.never }))
const vcs = yield* Vcs.Service
const fiber = yield* Effect.forkChild(vcs.status())
yield* Fiber.interrupt(fiber)
const exit = yield* Fiber.await(fiber)
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) expect(Cause.hasInterrupts(exit.cause)).toBe(true)
}).pipe(Effect.scoped, provide),
)
it.live("drops rows that fail schema validation", () =>
Effect.gen(function* () {
yield* register(