From f2cfd1a1cf068a22be3eddaa0834693db609ac04 Mon Sep 17 00:00:00 2001
From: LukeParkerDev <10430890+Hona@users.noreply.github.com>
Date: Tue, 17 Mar 2026 10:00:36 +1000
Subject: [PATCH 1/3] fix(opencode): isolate file runtimes from instance
bootstrap
Run File, FileTime, and Format through scoped instance runtimes so edit and file paths no longer initialize unrelated services. Update the affected tests to await async boundaries and drive file timestamps and lock ordering deterministically instead of relying on wall clock sleeps.
---
packages/opencode/src/effect/runtime.ts | 29 +++++++++++
packages/opencode/src/file/index.ts | 16 +++---
packages/opencode/src/file/time.ts | 12 +++--
packages/opencode/src/format/index.ts | 8 +--
packages/opencode/test/file/index.test.ts | 22 +++-----
packages/opencode/test/file/time.test.ts | 62 +++++++++++++++--------
packages/opencode/test/tool/edit.test.ts | 36 +++++++------
packages/opencode/test/tool/write.test.ts | 6 +--
8 files changed, 122 insertions(+), 69 deletions(-)
diff --git a/packages/opencode/src/effect/runtime.ts b/packages/opencode/src/effect/runtime.ts
index 02a7391d44c..53f26ec7a3b 100644
--- a/packages/opencode/src/effect/runtime.ts
+++ b/packages/opencode/src/effect/runtime.ts
@@ -1,6 +1,7 @@
import { Effect, Layer, ManagedRuntime } from "effect"
import { AccountService } from "@/account/service"
import { AuthService } from "@/auth/service"
+import { InstanceContext } from "@/effect/instance-context"
import { Instances } from "@/effect/instances"
import type { InstanceServices } from "@/effect/instances"
import { Instance } from "@/project/instance"
@@ -12,3 +13,31 @@ export const runtime = ManagedRuntime.make(
export function runPromiseInstance(effect: Effect.Effect) {
return runtime.runPromise(effect.pipe(Effect.provide(Instances.get(Instance.directory))))
}
+
+export function scoped(layer: Layer.Layer): (effect: Effect.Effect) => Promise
+export function scoped(
+ layer: Layer.Layer,
+ provide: Layer.Layer,
+): (effect: Effect.Effect) => Promise
+export function scoped(layer: Layer.Layer, provide?: Layer.Layer) {
+ const rt = Instance.state(
+ () => {
+ const ctx = Layer.sync(InstanceContext, () =>
+ InstanceContext.of({
+ directory: Instance.directory,
+ project: Instance.project,
+ }),
+ )
+ if (provide) {
+ return ManagedRuntime.make(Layer.fresh(layer).pipe(Layer.provide(ctx), Layer.provideMerge(provide), Layer.orDie))
+ }
+
+ return ManagedRuntime.make((Layer.fresh(layer).pipe(Layer.provide(ctx), Layer.orDie) as Layer.Layer))
+ },
+ (state) => state.dispose(),
+ )
+
+ return function (effect: Effect.Effect) {
+ return rt().runPromise(effect)
+ }
+}
diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts
index 44c04e9e434..e9989a4f73c 100644
--- a/packages/opencode/src/file/index.ts
+++ b/packages/opencode/src/file/index.ts
@@ -14,7 +14,7 @@ import { git } from "@/util/git"
import { Protected } from "./protected"
import { InstanceContext } from "@/effect/instance-context"
import { Effect, Layer, ServiceMap } from "effect"
-import { runPromiseInstance } from "@/effect/runtime"
+import { scoped } from "@/effect/runtime"
const log = Log.create({ service: "file" })
@@ -336,23 +336,23 @@ export namespace File {
}
export function init() {
- return runPromiseInstance(FileService.use((s) => s.init()))
+ return run(FileService.use((s) => s.init()))
}
export async function status() {
- return runPromiseInstance(FileService.use((s) => s.status()))
+ return run(FileService.use((s) => s.status()))
}
export async function read(file: string): Promise {
- return runPromiseInstance(FileService.use((s) => s.read(file)))
+ return run(FileService.use((s) => s.read(file)))
}
export async function list(dir?: string) {
- return runPromiseInstance(FileService.use((s) => s.list(dir)))
+ return run(FileService.use((s) => s.list(dir)))
}
export async function search(input: { query: string; limit?: number; dirs?: boolean; type?: "file" | "directory" }) {
- return runPromiseInstance(FileService.use((s) => s.search(input)))
+ return run(FileService.use((s) => s.search(input)))
}
}
@@ -448,7 +448,7 @@ export class FileService extends ServiceMap.Service kick())
})
const status = Effect.fn("FileService.status")(function* () {
@@ -722,3 +722,5 @@ export class FileService extends ServiceMap.Service s.read(sessionID, file)))
+ return run(FileTimeService.use((s) => s.read(sessionID, file)))
}
export function get(sessionID: SessionID, file: string) {
- return runPromiseInstance(FileTimeService.use((s) => s.get(sessionID, file)))
+ return run(FileTimeService.use((s) => s.get(sessionID, file)))
}
export async function assert(sessionID: SessionID, filepath: string) {
- return runPromiseInstance(FileTimeService.use((s) => s.assert(sessionID, filepath)))
+ return run(FileTimeService.use((s) => s.assert(sessionID, filepath)))
}
export async function withLock(filepath: string, fn: () => Promise): Promise {
- return runPromiseInstance(FileTimeService.use((s) => s.withLock(filepath, fn)))
+ return run(FileTimeService.use((s) => s.withLock(filepath, fn)))
}
}
diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts
index cb71fc363d8..aeb9a38974b 100644
--- a/packages/opencode/src/format/index.ts
+++ b/packages/opencode/src/format/index.ts
@@ -11,7 +11,7 @@ import { Instance } from "../project/instance"
import { Process } from "../util/process"
import { InstanceContext } from "@/effect/instance-context"
import { Effect, Layer, ServiceMap } from "effect"
-import { runPromiseInstance } from "@/effect/runtime"
+import { scoped } from "@/effect/runtime"
const log = Log.create({ service: "format" })
@@ -28,11 +28,11 @@ export namespace Format {
export type Status = z.infer
export async function init() {
- return runPromiseInstance(FormatService.use((s) => s.init()))
+ return run(FormatService.use((s) => s.init()))
}
export async function status() {
- return runPromiseInstance(FormatService.use((s) => s.status()))
+ return run(FormatService.use((s) => s.status()))
}
}
@@ -159,3 +159,5 @@ export class FormatService extends ServiceMap.Service {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- File.init()
- // Give the background scan time to populate
- await new Promise((r) => setTimeout(r, 500))
+ await File.init()
const result = await File.search({ query: "", type: "file" })
expect(result.length).toBeGreaterThan(0)
@@ -697,8 +695,7 @@ describe("file/index Filesystem patterns", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- File.init()
- await new Promise((r) => setTimeout(r, 500))
+ await File.init()
const result = await File.search({ query: "", type: "directory" })
expect(result.length).toBeGreaterThan(0)
@@ -718,8 +715,7 @@ describe("file/index Filesystem patterns", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- File.init()
- await new Promise((r) => setTimeout(r, 500))
+ await File.init()
const result = await File.search({ query: "main", type: "file" })
expect(result.some((f) => f.includes("main"))).toBe(true)
@@ -733,8 +729,7 @@ describe("file/index Filesystem patterns", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- File.init()
- await new Promise((r) => setTimeout(r, 500))
+ await File.init()
const result = await File.search({ query: "", type: "file" })
// Files don't end with /
@@ -751,8 +746,7 @@ describe("file/index Filesystem patterns", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- File.init()
- await new Promise((r) => setTimeout(r, 500))
+ await File.init()
const result = await File.search({ query: "", type: "directory" })
// Directories end with /
@@ -769,8 +763,7 @@ describe("file/index Filesystem patterns", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- File.init()
- await new Promise((r) => setTimeout(r, 500))
+ await File.init()
const result = await File.search({ query: "", type: "file", limit: 2 })
expect(result.length).toBeLessThanOrEqual(2)
@@ -784,8 +777,7 @@ describe("file/index Filesystem patterns", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- File.init()
- await new Promise((r) => setTimeout(r, 500))
+ await File.init()
const result = await File.search({ query: ".hidden", type: "directory" })
expect(result.length).toBeGreaterThan(0)
diff --git a/packages/opencode/test/file/time.test.ts b/packages/opencode/test/file/time.test.ts
index 2a3c56b2c5f..fbf8d5cd1e2 100644
--- a/packages/opencode/test/file/time.test.ts
+++ b/packages/opencode/test/file/time.test.ts
@@ -9,6 +9,19 @@ import { tmpdir } from "../fixture/fixture"
afterEach(() => Instance.disposeAll())
+async function touch(file: string, time: number) {
+ const date = new Date(time)
+ await fs.utimes(file, date, date)
+}
+
+function gate() {
+ let open!: () => void
+ const wait = new Promise((resolve) => {
+ open = resolve
+ })
+ return { open, wait }
+}
+
describe("file/time", () => {
const sessionID = SessionID.make("ses_00000000000000000000000001")
@@ -25,7 +38,6 @@ describe("file/time", () => {
expect(before).toBeUndefined()
await FileTime.read(sessionID, filepath)
- await Bun.sleep(10)
const after = await FileTime.get(sessionID, filepath)
expect(after).toBeInstanceOf(Date)
@@ -44,7 +56,6 @@ describe("file/time", () => {
fn: async () => {
await FileTime.read(SessionID.make("ses_00000000000000000000000002"), filepath)
await FileTime.read(SessionID.make("ses_00000000000000000000000003"), filepath)
- await Bun.sleep(10)
const time1 = await FileTime.get(SessionID.make("ses_00000000000000000000000002"), filepath)
const time2 = await FileTime.get(SessionID.make("ses_00000000000000000000000003"), filepath)
@@ -63,14 +74,10 @@ describe("file/time", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(sessionID, filepath)
- await Bun.sleep(10)
+ await FileTime.read(sessionID, filepath)
const first = await FileTime.get(sessionID, filepath)
- await Bun.sleep(10)
-
- FileTime.read(sessionID, filepath)
- await Bun.sleep(10)
+ await FileTime.read(sessionID, filepath)
const second = await FileTime.get(sessionID, filepath)
expect(second!.getTime()).toBeGreaterThanOrEqual(first!.getTime())
@@ -84,12 +91,12 @@ describe("file/time", () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "file.txt")
await fs.writeFile(filepath, "content", "utf-8")
+ await touch(filepath, 1_000)
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(sessionID, filepath)
- await Bun.sleep(10)
+ await FileTime.read(sessionID, filepath)
await FileTime.assert(sessionID, filepath)
},
})
@@ -112,13 +119,14 @@ describe("file/time", () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "file.txt")
await fs.writeFile(filepath, "content", "utf-8")
+ await touch(filepath, 1_000)
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(sessionID, filepath)
- await Bun.sleep(100)
+ await FileTime.read(sessionID, filepath)
await fs.writeFile(filepath, "modified content", "utf-8")
+ await touch(filepath, 2_000)
await expect(FileTime.assert(sessionID, filepath)).rejects.toThrow("modified since it was last read")
},
})
@@ -128,13 +136,14 @@ describe("file/time", () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "file.txt")
await fs.writeFile(filepath, "content", "utf-8")
+ await touch(filepath, 1_000)
await Instance.provide({
directory: tmp.path,
fn: async () => {
await FileTime.read(sessionID, filepath)
- await Bun.sleep(100)
await fs.writeFile(filepath, "modified", "utf-8")
+ await touch(filepath, 2_000)
let error: Error | undefined
try {
@@ -191,18 +200,25 @@ describe("file/time", () => {
directory: tmp.path,
fn: async () => {
const order: number[] = []
+ const hold = gate()
+ const ready = gate()
const op1 = FileTime.withLock(filepath, async () => {
order.push(1)
- await Bun.sleep(50)
+ ready.open()
+ await hold.wait
order.push(2)
})
+ await ready.wait
+
const op2 = FileTime.withLock(filepath, async () => {
order.push(3)
order.push(4)
})
+ hold.open()
+
await Promise.all([op1, op2])
expect(order).toEqual([1, 2, 3, 4])
},
@@ -219,15 +235,21 @@ describe("file/time", () => {
fn: async () => {
let started1 = false
let started2 = false
+ const hold = gate()
+ const ready = gate()
const op1 = FileTime.withLock(filepath1, async () => {
started1 = true
- await Bun.sleep(50)
+ ready.open()
+ await hold.wait
expect(started2).toBe(true)
})
+ await ready.wait
+
const op2 = FileTime.withLock(filepath2, async () => {
started2 = true
+ hold.open()
})
await Promise.all([op1, op2])
@@ -265,12 +287,12 @@ describe("file/time", () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "file.txt")
await fs.writeFile(filepath, "content", "utf-8")
+ await touch(filepath, 1_000)
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(sessionID, filepath)
- await Bun.sleep(10)
+ await FileTime.read(sessionID, filepath)
const stats = Filesystem.stat(filepath)
expect(stats?.mtime).toBeInstanceOf(Date)
@@ -285,17 +307,17 @@ describe("file/time", () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "file.txt")
await fs.writeFile(filepath, "original", "utf-8")
+ await touch(filepath, 1_000)
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(sessionID, filepath)
- await Bun.sleep(10)
+ await FileTime.read(sessionID, filepath)
const originalStat = Filesystem.stat(filepath)
- await Bun.sleep(100)
await fs.writeFile(filepath, "modified", "utf-8")
+ await touch(filepath, 2_000)
const newStat = Filesystem.stat(filepath)
expect(newStat!.mtime.getTime()).toBeGreaterThan(originalStat!.mtime.getTime())
diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts
index b0ee95ff6f7..7b6784cf49a 100644
--- a/packages/opencode/test/tool/edit.test.ts
+++ b/packages/opencode/test/tool/edit.test.ts
@@ -18,6 +18,11 @@ const ctx = {
ask: async () => {},
}
+async function touch(file: string, time: number) {
+ const date = new Date(time)
+ await fs.utimes(file, date, date)
+}
+
describe("tool.edit", () => {
describe("creating new files", () => {
test("creates new file when oldString is empty", async () => {
@@ -111,7 +116,7 @@ describe("tool.edit", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const edit = await EditTool.init()
const result = await edit.execute(
@@ -138,7 +143,7 @@ describe("tool.edit", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const edit = await EditTool.init()
await expect(
@@ -186,7 +191,7 @@ describe("tool.edit", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const edit = await EditTool.init()
await expect(
@@ -230,18 +235,17 @@ describe("tool.edit", () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "file.txt")
await fs.writeFile(filepath, "original content", "utf-8")
+ await touch(filepath, 1_000)
await Instance.provide({
directory: tmp.path,
fn: async () => {
// Read first
- FileTime.read(ctx.sessionID, filepath)
-
- // Wait a bit to ensure different timestamps
- await new Promise((resolve) => setTimeout(resolve, 100))
+ await FileTime.read(ctx.sessionID, filepath)
// Simulate external modification
await fs.writeFile(filepath, "modified externally", "utf-8")
+ await touch(filepath, 2_000)
// Try to edit with the new content
const edit = await EditTool.init()
@@ -267,7 +271,7 @@ describe("tool.edit", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const edit = await EditTool.init()
await edit.execute(
@@ -294,7 +298,7 @@ describe("tool.edit", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const { Bus } = await import("../../src/bus")
const { File } = await import("../../src/file")
@@ -332,7 +336,7 @@ describe("tool.edit", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const edit = await EditTool.init()
await edit.execute(
@@ -358,7 +362,7 @@ describe("tool.edit", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const edit = await EditTool.init()
await edit.execute(
@@ -407,7 +411,7 @@ describe("tool.edit", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(ctx.sessionID, dirpath)
+ await FileTime.read(ctx.sessionID, dirpath)
const edit = await EditTool.init()
await expect(
@@ -432,7 +436,7 @@ describe("tool.edit", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const edit = await EditTool.init()
const result = await edit.execute(
@@ -503,7 +507,7 @@ describe("tool.edit", () => {
fn: async () => {
const edit = await EditTool.init()
const filePath = path.join(tmp.path, "test.txt")
- FileTime.read(ctx.sessionID, filePath)
+ await FileTime.read(ctx.sessionID, filePath)
await edit.execute(
{
filePath,
@@ -644,7 +648,7 @@ describe("tool.edit", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const edit = await EditTool.init()
@@ -659,7 +663,7 @@ describe("tool.edit", () => {
)
// Need to read again since FileTime tracks per-session
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const promise2 = edit.execute(
{
diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts
index b93ab4e853e..af002a39100 100644
--- a/packages/opencode/test/tool/write.test.ts
+++ b/packages/opencode/test/tool/write.test.ts
@@ -99,7 +99,7 @@ describe("tool.write", () => {
directory: tmp.path,
fn: async () => {
const { FileTime } = await import("../../src/file/time")
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const write = await WriteTool.init()
const result = await write.execute(
@@ -128,7 +128,7 @@ describe("tool.write", () => {
directory: tmp.path,
fn: async () => {
const { FileTime } = await import("../../src/file/time")
- FileTime.read(ctx.sessionID, filepath)
+ await FileTime.read(ctx.sessionID, filepath)
const write = await WriteTool.init()
const result = await write.execute(
@@ -306,7 +306,7 @@ describe("tool.write", () => {
directory: tmp.path,
fn: async () => {
const { FileTime } = await import("../../src/file/time")
- FileTime.read(ctx.sessionID, readonlyPath)
+ await FileTime.read(ctx.sessionID, readonlyPath)
const write = await WriteTool.init()
await expect(
From 9fdf358d24c87875f9b2fef87976fd1e4daebf2f Mon Sep 17 00:00:00 2001
From: LukeParkerDev <10430890+Hona@users.noreply.github.com>
Date: Tue, 17 Mar 2026 10:25:59 +1000
Subject: [PATCH 2/3] test(opencode): deflake file and tool timing
Drop the scoped runtime experiment and keep the production Effect path unchanged. Stabilize the affected tests by awaiting FileTime reads, removing wall-clock timestamp races, and mocking plugin loading in the file/edit/write tests so unrelated provider bootstrap does not consume the 5s test budget.
---
packages/opencode/src/effect/runtime.ts | 29 -----------------------
packages/opencode/src/file/index.ts | 14 +++++------
packages/opencode/src/file/time.ts | 12 ++++------
packages/opencode/src/format/index.ts | 8 +++----
packages/opencode/test/file/index.test.ts | 12 ++++++++--
packages/opencode/test/file/time.test.ts | 12 ++++++++--
packages/opencode/test/tool/edit.test.ts | 14 ++++++++---
packages/opencode/test/tool/write.test.ts | 12 ++++++++--
8 files changed, 55 insertions(+), 58 deletions(-)
diff --git a/packages/opencode/src/effect/runtime.ts b/packages/opencode/src/effect/runtime.ts
index 53f26ec7a3b..02a7391d44c 100644
--- a/packages/opencode/src/effect/runtime.ts
+++ b/packages/opencode/src/effect/runtime.ts
@@ -1,7 +1,6 @@
import { Effect, Layer, ManagedRuntime } from "effect"
import { AccountService } from "@/account/service"
import { AuthService } from "@/auth/service"
-import { InstanceContext } from "@/effect/instance-context"
import { Instances } from "@/effect/instances"
import type { InstanceServices } from "@/effect/instances"
import { Instance } from "@/project/instance"
@@ -13,31 +12,3 @@ export const runtime = ManagedRuntime.make(
export function runPromiseInstance(effect: Effect.Effect) {
return runtime.runPromise(effect.pipe(Effect.provide(Instances.get(Instance.directory))))
}
-
-export function scoped(layer: Layer.Layer): (effect: Effect.Effect) => Promise
-export function scoped(
- layer: Layer.Layer,
- provide: Layer.Layer,
-): (effect: Effect.Effect) => Promise
-export function scoped(layer: Layer.Layer, provide?: Layer.Layer) {
- const rt = Instance.state(
- () => {
- const ctx = Layer.sync(InstanceContext, () =>
- InstanceContext.of({
- directory: Instance.directory,
- project: Instance.project,
- }),
- )
- if (provide) {
- return ManagedRuntime.make(Layer.fresh(layer).pipe(Layer.provide(ctx), Layer.provideMerge(provide), Layer.orDie))
- }
-
- return ManagedRuntime.make((Layer.fresh(layer).pipe(Layer.provide(ctx), Layer.orDie) as Layer.Layer))
- },
- (state) => state.dispose(),
- )
-
- return function (effect: Effect.Effect) {
- return rt().runPromise(effect)
- }
-}
diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts
index e9989a4f73c..cee03e0915a 100644
--- a/packages/opencode/src/file/index.ts
+++ b/packages/opencode/src/file/index.ts
@@ -14,7 +14,7 @@ import { git } from "@/util/git"
import { Protected } from "./protected"
import { InstanceContext } from "@/effect/instance-context"
import { Effect, Layer, ServiceMap } from "effect"
-import { scoped } from "@/effect/runtime"
+import { runPromiseInstance } from "@/effect/runtime"
const log = Log.create({ service: "file" })
@@ -336,23 +336,23 @@ export namespace File {
}
export function init() {
- return run(FileService.use((s) => s.init()))
+ return runPromiseInstance(FileService.use((s) => s.init()))
}
export async function status() {
- return run(FileService.use((s) => s.status()))
+ return runPromiseInstance(FileService.use((s) => s.status()))
}
export async function read(file: string): Promise {
- return run(FileService.use((s) => s.read(file)))
+ return runPromiseInstance(FileService.use((s) => s.read(file)))
}
export async function list(dir?: string) {
- return run(FileService.use((s) => s.list(dir)))
+ return runPromiseInstance(FileService.use((s) => s.list(dir)))
}
export async function search(input: { query: string; limit?: number; dirs?: boolean; type?: "file" | "directory" }) {
- return run(FileService.use((s) => s.search(input)))
+ return runPromiseInstance(FileService.use((s) => s.search(input)))
}
}
@@ -722,5 +722,3 @@ export class FileService extends ServiceMap.Service s.read(sessionID, file)))
+ return runPromiseInstance(FileTimeService.use((s) => s.read(sessionID, file)))
}
export function get(sessionID: SessionID, file: string) {
- return run(FileTimeService.use((s) => s.get(sessionID, file)))
+ return runPromiseInstance(FileTimeService.use((s) => s.get(sessionID, file)))
}
export async function assert(sessionID: SessionID, filepath: string) {
- return run(FileTimeService.use((s) => s.assert(sessionID, filepath)))
+ return runPromiseInstance(FileTimeService.use((s) => s.assert(sessionID, filepath)))
}
export async function withLock(filepath: string, fn: () => Promise): Promise {
- return run(FileTimeService.use((s) => s.withLock(filepath, fn)))
+ return runPromiseInstance(FileTimeService.use((s) => s.withLock(filepath, fn)))
}
}
diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts
index aeb9a38974b..cb71fc363d8 100644
--- a/packages/opencode/src/format/index.ts
+++ b/packages/opencode/src/format/index.ts
@@ -11,7 +11,7 @@ import { Instance } from "../project/instance"
import { Process } from "../util/process"
import { InstanceContext } from "@/effect/instance-context"
import { Effect, Layer, ServiceMap } from "effect"
-import { scoped } from "@/effect/runtime"
+import { runPromiseInstance } from "@/effect/runtime"
const log = Log.create({ service: "format" })
@@ -28,11 +28,11 @@ export namespace Format {
export type Status = z.infer
export async function init() {
- return run(FormatService.use((s) => s.init()))
+ return runPromiseInstance(FormatService.use((s) => s.init()))
}
export async function status() {
- return run(FormatService.use((s) => s.status()))
+ return runPromiseInstance(FormatService.use((s) => s.status()))
}
}
@@ -159,5 +159,3 @@ export class FormatService extends ServiceMap.Service ({
+ Plugin: {
+ list: async () => [],
+ init: async () => {},
+ },
+}))
+
+const { File } = await import("../../src/file")
+
describe("file/index Filesystem patterns", () => {
describe("File.read() - text content", () => {
test("reads text file via Filesystem.readText()", async () => {
diff --git a/packages/opencode/test/file/time.test.ts b/packages/opencode/test/file/time.test.ts
index fbf8d5cd1e2..2d7e41f397f 100644
--- a/packages/opencode/test/file/time.test.ts
+++ b/packages/opencode/test/file/time.test.ts
@@ -1,12 +1,20 @@
-import { describe, test, expect, afterEach } from "bun:test"
+import { describe, test, expect, afterEach, mock } from "bun:test"
import path from "path"
import fs from "fs/promises"
-import { FileTime } from "../../src/file/time"
import { Instance } from "../../src/project/instance"
import { SessionID } from "../../src/session/schema"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
+mock.module("../../src/plugin", () => ({
+ Plugin: {
+ list: async () => [],
+ init: async () => {},
+ },
+}))
+
+const { FileTime } = await import("../../src/file/time")
+
afterEach(() => Instance.disposeAll())
async function touch(file: string, time: number) {
diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts
index 7b6784cf49a..f7e8b9864ce 100644
--- a/packages/opencode/test/tool/edit.test.ts
+++ b/packages/opencode/test/tool/edit.test.ts
@@ -1,12 +1,20 @@
-import { describe, test, expect } from "bun:test"
+import { describe, test, expect, mock } from "bun:test"
import path from "path"
import fs from "fs/promises"
-import { EditTool } from "../../src/tool/edit"
import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture"
-import { FileTime } from "../../src/file/time"
import { SessionID, MessageID } from "../../src/session/schema"
+mock.module("../../src/plugin", () => ({
+ Plugin: {
+ list: async () => [],
+ init: async () => {},
+ },
+}))
+
+const { EditTool } = await import("../../src/tool/edit")
+const { FileTime } = await import("../../src/file/time")
+
const ctx = {
sessionID: SessionID.make("ses_test-edit-session"),
messageID: MessageID.make(""),
diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts
index af002a39100..4e3b5a1ad22 100644
--- a/packages/opencode/test/tool/write.test.ts
+++ b/packages/opencode/test/tool/write.test.ts
@@ -1,11 +1,19 @@
-import { describe, test, expect } from "bun:test"
+import { describe, test, expect, mock } from "bun:test"
import path from "path"
import fs from "fs/promises"
-import { WriteTool } from "../../src/tool/write"
import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture"
import { SessionID, MessageID } from "../../src/session/schema"
+mock.module("../../src/plugin", () => ({
+ Plugin: {
+ list: async () => [],
+ init: async () => {},
+ },
+}))
+
+const { WriteTool } = await import("../../src/tool/write")
+
const ctx = {
sessionID: SessionID.make("ses_test-write-session"),
messageID: MessageID.make(""),
From e2bab8b5373462b8283ff3422a5099a2eb85722a Mon Sep 17 00:00:00 2001
From: LukeParkerDev <10430890+Hona@users.noreply.github.com>
Date: Tue, 17 Mar 2026 10:36:29 +1000
Subject: [PATCH 3/3] test(opencode): disable default plugins in tests
Default plugin loading was consuming much of the 5s budget in file and tool tests, and local module mocks leaked into unrelated suites. Disable default plugins in test preload so the existing runtime stays intact, then keep the deterministic await and filesystem timestamp cleanup in the affected tests.
---
packages/opencode/test/file/index.test.ts | 12 ++----------
packages/opencode/test/file/time.test.ts | 12 ++----------
packages/opencode/test/preload.ts | 1 +
packages/opencode/test/tool/edit.test.ts | 14 +++-----------
packages/opencode/test/tool/write.test.ts | 12 ++----------
5 files changed, 10 insertions(+), 41 deletions(-)
diff --git a/packages/opencode/test/file/index.test.ts b/packages/opencode/test/file/index.test.ts
index 006cc8cf333..8f4cbe8688c 100644
--- a/packages/opencode/test/file/index.test.ts
+++ b/packages/opencode/test/file/index.test.ts
@@ -1,20 +1,12 @@
-import { describe, test, expect, mock } from "bun:test"
+import { describe, test, expect } from "bun:test"
import { $ } from "bun"
import path from "path"
import fs from "fs/promises"
+import { File } from "../../src/file"
import { Instance } from "../../src/project/instance"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
-mock.module("../../src/plugin", () => ({
- Plugin: {
- list: async () => [],
- init: async () => {},
- },
-}))
-
-const { File } = await import("../../src/file")
-
describe("file/index Filesystem patterns", () => {
describe("File.read() - text content", () => {
test("reads text file via Filesystem.readText()", async () => {
diff --git a/packages/opencode/test/file/time.test.ts b/packages/opencode/test/file/time.test.ts
index 2d7e41f397f..fbf8d5cd1e2 100644
--- a/packages/opencode/test/file/time.test.ts
+++ b/packages/opencode/test/file/time.test.ts
@@ -1,20 +1,12 @@
-import { describe, test, expect, afterEach, mock } from "bun:test"
+import { describe, test, expect, afterEach } from "bun:test"
import path from "path"
import fs from "fs/promises"
+import { FileTime } from "../../src/file/time"
import { Instance } from "../../src/project/instance"
import { SessionID } from "../../src/session/schema"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
-mock.module("../../src/plugin", () => ({
- Plugin: {
- list: async () => [],
- init: async () => {},
- },
-}))
-
-const { FileTime } = await import("../../src/file/time")
-
afterEach(() => Instance.disposeAll())
async function touch(file: string, time: number) {
diff --git a/packages/opencode/test/preload.ts b/packages/opencode/test/preload.ts
index 1ebd273d266..e253183d8d0 100644
--- a/packages/opencode/test/preload.ts
+++ b/packages/opencode/test/preload.ts
@@ -44,6 +44,7 @@ process.env["OPENCODE_TEST_HOME"] = testHome
// Set test managed config directory to isolate tests from system managed settings
const testManagedConfigDir = path.join(dir, "managed")
process.env["OPENCODE_TEST_MANAGED_CONFIG_DIR"] = testManagedConfigDir
+process.env["OPENCODE_DISABLE_DEFAULT_PLUGINS"] = "true"
// Write the cache version file to prevent global/index.ts from clearing the cache
const cacheDir = path.join(dir, "cache", "opencode")
diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts
index f7e8b9864ce..7b6784cf49a 100644
--- a/packages/opencode/test/tool/edit.test.ts
+++ b/packages/opencode/test/tool/edit.test.ts
@@ -1,20 +1,12 @@
-import { describe, test, expect, mock } from "bun:test"
+import { describe, test, expect } from "bun:test"
import path from "path"
import fs from "fs/promises"
+import { EditTool } from "../../src/tool/edit"
import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture"
+import { FileTime } from "../../src/file/time"
import { SessionID, MessageID } from "../../src/session/schema"
-mock.module("../../src/plugin", () => ({
- Plugin: {
- list: async () => [],
- init: async () => {},
- },
-}))
-
-const { EditTool } = await import("../../src/tool/edit")
-const { FileTime } = await import("../../src/file/time")
-
const ctx = {
sessionID: SessionID.make("ses_test-edit-session"),
messageID: MessageID.make(""),
diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts
index 4e3b5a1ad22..af002a39100 100644
--- a/packages/opencode/test/tool/write.test.ts
+++ b/packages/opencode/test/tool/write.test.ts
@@ -1,19 +1,11 @@
-import { describe, test, expect, mock } from "bun:test"
+import { describe, test, expect } from "bun:test"
import path from "path"
import fs from "fs/promises"
+import { WriteTool } from "../../src/tool/write"
import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture"
import { SessionID, MessageID } from "../../src/session/schema"
-mock.module("../../src/plugin", () => ({
- Plugin: {
- list: async () => [],
- init: async () => {},
- },
-}))
-
-const { WriteTool } = await import("../../src/tool/write")
-
const ctx = {
sessionID: SessionID.make("ses_test-write-session"),
messageID: MessageID.make(""),