#18524 · @sjawhar · opened Mar 21, 2026 at 1:32 PM UTC · last updated Mar 21, 2026 at 1:34 PM UTC

fix(bundler): rewrite namespace re-exports for Bun bundler compatibility

appfix
60
+2802579 files

Score breakdown

Impact

9.0

Clarity

9.0

Urgency

9.0

Ease Of Review

8.0

Guidelines

9.0

Readiness

8.0

Size

0.0

Trust

5.0

Traction

2.0

Summary

This PR addresses a critical crash in the Bun-bundled binary caused by namespace re-exports evaluating to undefined. It rewrites 9 facade files to separate value and type exports, ensuring correct module initialization order for Bun's bundler. The fix preserves the identical public API surface and resolves a blocker for Bun users.

Open in GitHub

Description

Issue for this PR

Closes #18525

Type of change

  • [x] Bug fix
  • [ ] New feature
  • [ ] Refactor / code improvement
  • [ ] Documentation

What does this PR do?

Rewrites 9 facade/index files that re-export values from service modules inside TypeScript namespaces. Bun's bundler transpiles namespace re-exports like export const FileDiff = S.FileDiff into self-referential assignments (Snapshot.FileDiff = Snapshot.FileDiff) which evaluate to undefined when circular imports delay module initialization.

The fix splits each file into:

  • A plain export const X = { ... } object literal with value exports (eagerly initialized with correct references)
  • A separate export namespace X { ... } block with type-only exports (erased at runtime)

This preserves the identical public API surface while ensuring Bun's bundler emits correct initialization code regardless of module evaluation order.

Files changed: src/snapshot/index.ts, src/question/index.ts, src/provider/auth.ts, src/file/index.ts, src/file/time.ts, src/skill/skill.ts, src/tool/truncate.ts, src/permission/index.ts, src/format/index.ts

How did you verify your code works?

  1. bun typecheck — passes clean (tsgo --noEmit)
  2. bun run build --single — builds successfully
  3. Bundled binary opencode serve — starts and listens without TypeError (previously crashed with Snapshot2.FileDiff.array and ProviderAuth2.Authorization.optional errors)
  4. Bundled binary opencode run "echo test" — creates session, runs LLM prompt loop, executes tools successfully

Screenshots / recordings

N/A — not a UI change.

Checklist

  • [x] I have tested my changes locally
  • [x] I have not included unrelated changes in this PR

Linked Issues

#18525 Bundled binary crashes with TypeError on namespace re-exports

View issue

Comments

No comments.

Changed Files

packages/opencode/src/file/index.ts

+3431
@@ -1,40 +1,43 @@
import { runPromiseInstance } from "@/effect/runtime"
import { File as S } from "./service"
import * as Mod from "./service"
export namespace File {
export const Info = S.Info
export type Info = S.Info
export const Node = S.Node
export type Node = S.Node
export const Content = S.Content
export type Content = S.Content
export const Event = S.Event
export type Interface = S.Interface
const init = () => {
return runPromiseInstance(Mod.File.Service.use((svc) => svc.init()))
}
export const Service = S.Service
export const layer = S.layer
const status = async () => {
return runPromiseInstance(Mod.File.Service.use((svc) => svc.status()))
}
export function init() {
return runPromiseInstance(S.Service.use((svc) => svc.init()))
}
const read = async (file: string): Promise<File.Content> => {
return runPromiseInstance(Mod.File.Service.use((svc) => svc.read(file)))
}
export async function status() {
return runPromiseInstance(S.Service.use((svc) => svc.status()))
}
const list = async (dir?: string) => {
return runPromiseInstance(Mod.File.Service.use((svc) => svc.list(dir)))
}
export

packages/opencode/src/file/time.ts

+2419
@@ -1,28 +1,33 @@
import { runPromiseInstance } from "@/effect/runtime"
import type { SessionID } from "@/session/schema"
import { FileTime as S } from "./time-service"
import * as Mod from "./time-service"
export namespace FileTime {
export type Stamp = S.Stamp
export type Interface = S.Interface
const read = (sessionID: SessionID, file: string) => {
return runPromiseInstance(Mod.FileTime.Service.use((s) => s.read(sessionID, file)))
}
export const Service = S.Service
export const layer = S.layer
const get = (sessionID: SessionID, file: string) => {
return runPromiseInstance(Mod.FileTime.Service.use((s) => s.get(sessionID, file)))
}
export function read(sessionID: SessionID, file: string) {
return runPromiseInstance(S.Service.use((s) => s.read(sessionID, file)))
}
const assert = async (sessionID: SessionID, filepath: string) => {
return runPromiseInstance(Mod.FileTime.Service.use((s) => s.assert(sessionID, filepath)))
}
export function get(sessionID: SessionID, file: string) {
return runPromiseInstance(S.Service.use((s) => s.get(sessionID, file)))
}
const withLock = async <T>(filepath: string, fn: () => Promise<T>): P

packages/opencode/src/format/index.ts

+1311
@@ -1,16 +1,18 @@
import { runPromiseInstance } from "@/effect/runtime"
import { Format as S } from "./service"
import * as Mod from "./service"
export namespace Format {
export const Status = S.Status
export type Status = S.Status
export type Interface = S.Interface
const status = async () => {
return runPromiseInstance(Mod.Format.Service.use((s) => s.status()))
}
export const Service = S.Service
export const layer = S.layer
export const Format = {
Status: Mod.Format.Status,
Service: Mod.Format.Service,
layer: Mod.Format.layer,
status,
}
export async function status() {
return runPromiseInstance(S.Service.use((s) => s.status()))
}
export namespace Format {
export type Status = Mod.Format.Status
export type Interface = Mod.Format.Interface
}

packages/opencode/src/permission/index.ts

+4445
@@ -1,52 +1,51 @@
import { runPromiseInstance } from "@/effect/runtime"
import { fn } from "@/util/fn"
import z from "zod"
import { Permission as S } from "./service"
import * as Mod from "./service"
export namespace PermissionNext {
export const Action = S.Action
export type Action = S.Action
export const Rule = S.Rule
export type Rule = S.Rule
export const Ruleset = S.Ruleset
export type Ruleset = S.Ruleset
export const Request = S.Request
export type Request = S.Request
export const Reply = S.Reply
export type Reply = S.Reply
export const Approval = S.Approval
export type Approval = z.infer<typeof S.Approval>
export const Event = S.Event
export const RejectedError = S.RejectedError
export const CorrectedError = S.CorrectedError
export const DeniedError = S.DeniedError
export type Error = S.Error
export const AskInput = S.AskInput
export const ReplyInput = S.ReplyInput
export type Interface = S.Interface
export const Service = S.Service
export const layer = S.layer
export const evaluate = S.evaluate
export const fromConfig = S.fromConfig
export const merge = S.merge
export

packages/opencode/src/provider/auth.ts

+4240
@@ -2,47 +2,49 @@ import { runPromiseInstance } from "@/effect/runtime"
import { fn } from "@/util/fn"
import { ProviderID } from "./schema"
import z from "zod"
import { ProviderAuth as S } from "./auth-service"
import * as Mod from "./auth-service"
export namespace ProviderAuth {
export const Method = S.Method
export type Method = S.Method
export const Authorization = S.Authorization
export type Authorization = S.Authorization
export const OauthMissing = S.OauthMissing
export const OauthCodeMissing = S.OauthCodeMissing
export const OauthCallbackFailed = S.OauthCallbackFailed
export const ValidationFailed = S.ValidationFailed
export type Error = S.Error
export type Interface = S.Interface
export const Service = S.Service
export const layer = S.layer
export const defaultLayer = S.defaultLayer
export async function methods() {
return runPromiseInstance(S.Service.use((svc) => svc.methods()))
}
const methods = async () => {
return runPromiseInstance(Mod.ProviderAuth.Service.use((svc) => svc.methods()))
}
export const authorize = fn(
z.object({
providerID: ProviderID.zod,
method: z.number(),

packages/opencode/src/question/index.ts

+4040
@@ -1,49 +1,49 @@
import { runPromiseInstance } from "@/effect/runtime"
import type { MessageID, SessionID } from "@/session/schema"
import type { QuestionID } from "./schema"
import { Question as S } from "./service"
export namespace Question {
export const Option = S.Option
export type Option = S.Option
export const Info = S.Info
export type Info = S.Info
export const Request = S.Request
export type Request = S.Request
export const Answer = S.Answer
export type Answer = S.Answer
export const Reply = S.Reply
export type Reply = S.Reply
export const Event = S.Event
export const RejectedError = S.RejectedError
export type Interface = S.Interface
import * as Q from "./service"
const ask = async (input: {
sessionID: SessionID
questions: Question.Info[]
tool?: { messageID: MessageID; callID: string }
}): Promise<Question.Answer[]> => {
return runPromiseInstance(Q.Question.Service.use((s) => s.ask(input)))
}
export const Service = S.Service
export const layer = S.layer
const reply = async (input: { requestID: QuestionID; answers: Question.Answer[] }) => {
return runPromiseInstance(Q.Question.Service.

packages/opencode/src/skill/skill.ts

+2926
@@ -1,35 +1,38 @@
import { runPromiseInstance } from "@/effect/runtime"
import type { Agent } from "@/agent/agent"
import { Skill as S } from "./service"
import * as Mod from "./service"
export namespace Skill {
export const Info = S.Info
export type Info = S.Info
export const InvalidError = S.InvalidError
export const NameMismatchError = S.NameMismatchError
export type Interface = S.Interface
export const Service = S.Service
export const layer = S.layer
export const defaultLayer = S.defaultLayer
const get = async (name: string) => {
return runPromiseInstance(Mod.Skill.Service.use((skill) => skill.get(name)))
}
export const fmt = S.fmt
const all = async () => {
return runPromiseInstance(Mod.Skill.Service.use((skill) => skill.all()))
}
export async function get(name: string) {
return runPromiseInstance(S.Service.use((skill) => skill.get(name)))
}
const dirs = async () => {
return runPromiseInstance(Mod.Skill.Service.use((skill) => skill.dirs()))
}
export async function all() {
return runPromiseInstance(S.Service.use((skill) => skill.all()))
}
const available = async (agent?: Agent.Info) => {
retur

packages/opencode/src/snapshot/index.ts

+4033
@@ -1,44 +1,51 @@
import { runPromiseInstance } from "@/effect/runtime"
import { Snapshot as S } from "./service"
import * as Snap from "./service"
export namespace Snapshot {
export const Patch = S.Patch
export type Patch = S.Patch
export const FileDiff = S.FileDiff
export type FileDiff = S.FileDiff
export type Interface = S.Interface
const cleanup = async () => {
return runPromiseInstance(Snap.Snapshot.Service.use((svc) => svc.cleanup()))
}
export const Service = S.Service
export const layer = S.layer
export const defaultLayer = S.defaultLayer
const track = async () => {
return runPromiseInstance(Snap.Snapshot.Service.use((svc) => svc.track()))
}
export async function cleanup() {
return runPromiseInstance(S.Service.use((svc) => svc.cleanup()))
}
const patch = async (hash: string) => {
return runPromiseInstance(Snap.Snapshot.Service.use((svc) => svc.patch(hash)))
}
export async function track() {
return runPromiseInstance(S.Service.use((svc) => svc.track()))
}
const restore = async (snapshot: string) => {
return runPromiseInstance(Snap.Snapshot.Service.use((svc) => svc.restore(snapshot)))
}
expo

packages/opencode/src/tool/truncate.ts

+1412
@@ -1,18 +1,20 @@
import type { Agent } from "../agent/agent"
import { runtime } from "@/effect/runtime"
import { Truncate as S } from "./truncate-effect"
import * as Mod from "./truncate-effect"
export namespace Truncate {
export const MAX_LINES = S.MAX_LINES
export const MAX_BYTES = S.MAX_BYTES
export const DIR = S.DIR
export const GLOB = S.GLOB
export type Result = S.Result
const output = async (text: string, options: Truncate.Options = {}, agent?: Agent.Info): Promise<Truncate.Result> => {
return runtime.runPromise(Mod.Truncate.Service.use((s) => s.output(text, options, agent)))
}
export type Options = S.Options
export const Truncate = {
MAX_LINES: Mod.Truncate.MAX_LINES,
MAX_BYTES: Mod.Truncate.MAX_BYTES,
DIR: Mod.Truncate.DIR,
GLOB: Mod.Truncate.GLOB,
output,
}
export async function output(text: string, options: Options = {}, agent?: Agent.Info): Promise<Result> {
return runtime.runPromise(S.Service.use((s) => s.output(text, options, agent)))
}
export namespace Truncate {
export type Result = Mod.Truncate.Result
export type Options = Mod.Truncate.Options
}