Short roadmap for Effect cleanup in packages/opencode.
Current patterns and examples live in guide.md. Error
boundary migration details live in
error-boundaries-plan.md. Test migration rules live in
test/EFFECT_TEST_MIGRATION.md.
Older deep-dive notes in this directory may still be useful, but treat
this roadmap and the guide as the current entry points.
This is a planning map, not a verified inventory. Before starting a task,
re-run a targeted git grep from current dev and update this file if
the inventory changed.
P0 ERR + RENDER + HTTP
Make expected failures typed, render them well, and stop relying on
generic HTTP error guesswork.
P1 TEST
Convert touched tests to the ideal Effect test patterns from the guide.
P2 RF
Move mutable runtime flags into typed runtime/config services.
P3 GLOBAL
Make global paths explicit and remove import-time side effects.
P4 INST + BRIDGE
Remove ambient Instance coupling while keeping Promise/callback interop.
P5 PROC + FS
Replace raw process/filesystem edges with typed Effect services.
P6 OA
Shrink OpenAPI compatibility shims as source schemas improve.
ERR Typed errors — replace legacy NamedError.create(...) and
Effect.die(...) for expected service failures with
Schema.TaggedErrorClass errors on the Effect error channel.
Shrinks: NamedError usage.RENDER User-visible error rendering — preserve structured typed-error
details at CLI, HTTP, and tool boundaries.
Shrinks: opaque Error: Name rendering.HTTP HTTP route cleanup — make route errors explicit instead of
relying on generic middleware to guess status/body from error names.
Shrinks: middleware/error.ts
and route-level compatibility shims.TEST Effect test migration — use testEffect, it.live, and
it.instance with explicit layers.
Shrinks: Promise-style tests, sleeps, mutable global test flags.RF RuntimeFlags / Flag deletion — move mutable
Flag reads into typed runtime/config
services.
Shrinks: flag.ts,
test/fixture/flag.ts.GLOBAL Global paths / import side effects — make global path state
explicit and testable instead of mutable module state.
Shrinks: global.ts import-time side
effects, mutable Global.Path overrides, and its Flag dependency.INST Instance shim — remove ambient Instance usage and old ALS
access patterns.
Shrinks: src/project/instance.ts.BRIDGE Promise/callback interop — keep bridge helpers, but reduce
legacy ALS coupling.
Shrinks: src/effect/bridge.ts
dependency on project/instance.ts.PROC AppProcess migration — prefer AppProcess.Service over raw
process wrappers.
Shrinks: direct spawn callsites and legacy process helpers.FS AppFileSystem migration — prefer AppFileSystem.Service over raw
filesystem APIs.
Shrinks: direct fs / Bun.file service callsites where inappropriate.RT Runtime/facade cleanup — remove service-local makeRuntime
facades when not intentional.
Shrinks: async facade exports around services and
run-service.ts usage.OA OpenAPI compatibility — tighten source schemas instead of
post-processing generated OpenAPI.
Shrinks: schema workaround blocks in
public.ts.This should be the next big cleanup theme. The codebase is moving toward typed Effect failures, but the user-facing boundaries still leak old shapes and sometimes collapse rich errors into opaque strings.
NamedError.create(...).Effect.die(...), which
makes them defects instead of typed, recoverable failures.Error: SomeName output.Worktree* or ProviderAuthValidationFailed.Schema.TaggedErrorClass.Error union and include it in method return types.Effect.die(...) is reserved for defects: bugs, impossible states,
violated invariants, or final unknown-boundary fallbacks.Effect.gen / Effect.fn, use yield* new MyError(...) for
direct expected failures.HttpApiError, or
route-specific error schemas.RENDER-1 Fix CLI top-level rendering for typed config errors.ERR-1 Convert storage/storage.ts
not-found errors.ERR-2 Convert worktree/index.ts
errors and remove matching HTTP name checks where possible.ERR-3 Convert provider/auth.ts
validation errors.HTTP-1 Remove the unknown-500 stack leak from
middleware/error.ts.HTTP-2 Audit one route group for explicit error contracts and
decide which mappings stay inline vs. shared helper.When touching tests, migrate them toward the ideal patterns in
test/EFFECT_TEST_MIGRATION.md:
testEffect(...) with explicit layers.it.instance(...) for service tests that need an instance.it.live(...) for real timers, filesystem mtimes, child
processes, git, locks, or other live integration behavior.process.env or mutable globals after layers are built.RuntimeFlags.layer(...), for
behavior changes.Recently completed:
Recommended next PRs:
RF-1 scout consumers ─┐
├─ can run in parallel
RF-2 plan-mode prompt ┘
└─ RF-3 event-system cluster, stacked only if RF-2 still touches prompt.ts
RF-4 workspaces cluster: later, after mutable Flag tests are cleaned up
RF-1 Move scout reads in agent.ts
and reference.ts.RF-2 Move plan-mode prompt read in
session/prompt.ts.RF-3 Move event-system reads in session prompt/processor/
compaction and TUI debug plugin.RF-4 Move workspaces reads in session/sync/control-plane after
tests stop relying on mutable Flag timing.test/fixture/flag.ts once
tests no longer mutate Flag.flag.ts once no packages
import it.global.ts is real connective tissue, not
just cosmetic ugliness. It currently mixes path calculation, import-time
directory creation, Flock setup, mutable exported Path state, and a
Flag dependency.
Problems to reduce:
Global.Path by mutating exported module state.Global.Path directly instead of the Effect service.Global.make() still reads mutable Flag.OPENCODE_CONFIG_DIR.Next PR candidates:
Global.Path test overrides with explicit test
layers or scoped helpers.Flock setup behind an explicit init
boundary where possible.Flag dependency from global path resolution.project/instance.ts is the deletion
target. effect/bridge.ts is not a near-term
deletion target; Promise/callback interop will continue to exist.
Goal:
Instance.restore / Instance.current.InstanceRef, WorkspaceRef, InstanceState, or
explicit context where practical.project/instance.ts only after ambient Instance coupling is gone.Important distinction:
InstanceState.context, InstanceState.directory, and
InstanceState.workspaceID are acceptable inside normal Effect service
code when InstanceRef / WorkspaceRef are provided by the runtime.Instance.* reads, InstanceState.bind(...),
AppRuntime.runPromise(...) re-entry from plain JS, and bridge restore
code that installs legacy ALS before invoking callbacks.Current bottom-up inventory from dev:
Instance.* value readers:
tool/repo_overview.ts,
control-plane/adapters/worktree.ts,
cli/bootstrap.ts.InstanceState.bind(...) callback boundaries:
file/watcher.ts native watcher callback,
storage/db.ts transaction/effect callbacks,
session/llm.ts workflow approval callback.AppRuntime.runPromise(...) / re-entry from plain JS:
project/with-instance.ts,
project/instance-runtime.ts,
control-plane/adapters/worktree.ts,
cli/effect-cmd.ts, plus global/non-instance
callsites such as CLI upgrade and ACP agent defaults.control-plane/workspace.ts,
MCP, command execution, plugins, pty lifecycle, bus scope cleanup, task
cancellation, and HTTP lifecycle reload/dispose paths.effect/run-service.ts,
effect/bridge.ts, and
effect/instance-state.ts.Recommended PR order:
INST-1 Remove direct Instance.* value readers. Start with
repo_overview, worktree adapter, and cli/bootstrap; pass context
explicitly or obtain it from an Effect boundary.INST-2 Move type-only InstanceContext imports from
project/instance.ts to
project/instance-context.ts.INST-3 Audit each InstanceState.bind(...) callback from the inside
out: list what the callback calls (Bus.publish, database effects,
permission/session services), then replace ambient capture with explicit
InstanceRef / WorkspaceRef provision or an EffectBridge call.INST-4 Classify AppRuntime.runPromise(...) callsites as global,
instance-scoped with explicit refs, or bridge-required. Eliminate the
instance-scoped callsites that rely on run-service.attach() falling
back to Instance.current.INST-5 After consumers are explicit, remove Instance.current fallback
from InstanceState.context and run-service.attach().INST-6 Move any remaining restore / bind compatibility helpers to
the boundary that still needs them, then delete
project/instance.ts.PROC / FS — continue AppProcess and AppFileSystem migrations as
focused PRs when touching relevant files.RT — remove service-local runtime facades only when they are not an
intentional boundary.OA — shrink public.ts
by tightening source schemas one workaround at a time.fetch → HttpClient — migrate raw fetch callsites when the caller is
already effectful or being effectified.Tools — remaining tool cleanup is narrow: webfetch HTML extraction
and shell raw stream/promise edges.