Skip to content

refactor(instance): add Effect foundation and effectCmd proof#507

Merged
Astro-Han merged 4 commits into
devfrom
codex/i477-pr5-effect-httpapi
May 9, 2026
Merged

refactor(instance): add Effect foundation and effectCmd proof#507
Astro-Han merged 4 commits into
devfrom
codex/i477-pr5-effect-httpapi

Conversation

@Astro-Han
Copy link
Copy Markdown
Owner

@Astro-Han Astro-Han commented May 9, 2026

Summary

  • add an Effect-backed InstanceBootstrap / InstanceStore foundation for instance boot, reload, dispose, and context propagation
  • route legacy Hono, CLI, worktree, watcher, tool, and test boundaries through the new instance bootstrap path
  • add a minimal effectCmd wrapper and migrate only the models command as the proof chain
  • split HttpApi parity out of this PR after the split trigger: importing it pulled in excluded listener/session/warp/workspace chains

Why

This is the PR5 foundation slice for #477. The goal is to absorb the upstream Effect foundation and the smallest effectCmd proof without switching the default server path.

The important boundary is that this PR touches production Hono and CLI bootstrap paths, so it is reviewed as a production-path runtime change. It does not claim a HttpApi rollback surface and does not introduce the non-default HttpApi backend.

Related Issue

Refs #477

Human Review Status

Pending. A human should make the final merge decision after reviewing the final diff and verification evidence.

Review Focus

  • InstanceStore load, reload, dispose, and failed-boot behavior
  • bootstrap ordering for plugins, config hooks, file watcher, VCS, Hono middleware, CLI, and worktree reload paths
  • Effect instance context bridging across async JS callbacks and nested AppRuntime.runPromise
  • effectCmd wrapper behavior and the minimal models command migration
  • excluded scope: no HttpApi backend, listener switch, desktop/app consolidation, v2 session/warp semantics, or broad CLI migration

Risk Notes

  • This is not hidden behind the future HttpApi flag: the foundation changes affect default Hono and CLI production paths.
  • Hono remains the default server path and fallback. HttpApi parity is intentionally split to the next PR.
  • No dependency, generated file, desktop/app, package consolidation, listener-default, or v2/warp changes are included.
  • macOS and Windows impact was considered for paths, shells, permissions, and worktrees. Local verification ran on macOS; Windows-specific coverage remains with CI.

How To Verify

Typecheck: bun --cwd packages/opencode typecheck -> exit 0
Focused tests: bun --cwd packages/opencode test test/project/state.test.ts test/project/instance-store.test.ts test/project/instance-bootstrap-regression.test.ts test/cli/effect-cmd-instance-als.test.ts test/cli/error.test.ts -> 19 pass, 0 fail
Full opencode tests: bun --cwd packages/opencode test -> 2699 pass, 9 skip, 1 todo, 0 fail across 207 files
Diff check: git diff --check -> no whitespace errors
HttpApi exclusion scan: rg -n "HttpApi|httpapi|httpapi-server|#httpapi" packages/opencode/src packages/opencode/test packages/opencode/package.json -> no matches
Warp/session exclusion scan: rg -n "sessionWarp|/sync/steal|warp UI|local-project detach|workspace-aware patch apply" packages/opencode/src packages/opencode/test -> no matches

Screenshots or Recordings

Not required. No visible UI changes.

Checklist

  • Human review status is stated above as pending, approved, or not required
  • I linked the related issue, or stated why there is no issue
  • This PR has type, primary area, and priority labels, or I requested maintainer labeling
  • I described the review focus and any meaningful risks
  • I listed the relevant verification steps and the key result for each
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope
  • I manually checked visible UI or copy changes when needed, with screenshots or recordings
  • I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes
  • I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant
  • I reviewed the final diff for unrelated changes and suspicious dependency changes
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of missing instance contexts in event publishing with fallback behavior.
    • Enhanced error formatting for CLI commands with proper exit code support.
  • Refactor

    • Refactored instance lifecycle management to use Effect-based architecture for better composability.
    • Updated models CLI command to use new Effect-based command handler pattern.
    • Improved file watcher and service initialization with instance context awareness.

Review Change Stack

@Astro-Han Astro-Han added P1 High priority upstream Tracked upstream or vendor behavior task Maintainer or agent execution task labels May 9, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

Warning

Rate limit exceeded

@Astro-Han has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 59 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c518b046-6ff2-4974-8690-c7be713a2f9a

📥 Commits

Reviewing files that changed from the base of the PR and between 8e46a0f and 3b48455.

📒 Files selected for processing (14)
  • packages/opencode/src/cli/cmd/models.ts
  • packages/opencode/src/cli/effect-cmd.ts
  • packages/opencode/src/effect/run-service.ts
  • packages/opencode/src/file/index.ts
  • packages/opencode/src/file/watcher.ts
  • packages/opencode/src/project/bootstrap-service.ts
  • packages/opencode/src/project/bootstrap.ts
  • packages/opencode/src/project/instance-layer.ts
  • packages/opencode/src/project/instance-store.ts
  • packages/opencode/src/project/instance.ts
  • packages/opencode/src/project/with-instance.ts
  • packages/opencode/test/file/index.test.ts
  • packages/opencode/test/project/instance-bootstrap-regression.test.ts
  • packages/opencode/test/project/instance-store.test.ts
📝 Walkthrough

Walkthrough

This PR refactors the instance provisioning system from callback-based InstanceBootstrap initialization to an Effect/Layer-based service with centralized lifecycle management. InstanceStore coordinates per-directory instance loading with deferred coordination and single-flight semantics. CLI commands are integrated with a new effectCmd builder that conditionally provides instance context. Bus event publishing now handles missing instance context gracefully via a global fallback event. All explicit init: InstanceBootstrap arguments are removed from call sites throughout the codebase.

Changes

Instance Lifecycle and Bootstrap Service Refactor

Layer / File(s) Summary
Data Contracts
packages/opencode/src/project/instance-context.ts, packages/opencode/src/project/bootstrap-service.ts
InstanceContext interface tracks directory, worktree, and project metadata; BootstrapService defines run: Effect.Effect member via typed Effect Context.Service.
Bootstrap Effect/Layer Pipeline
packages/opencode/src/project/bootstrap.ts
Bootstrap orchestration refactored to Effect.gen with a boot wrapper that catches causes and logs errors; services (Bus, Config, Plugin, File, FileWatcher, Vcs, Snapshot, etc.) are wired from Effect environment; concurrent initialization via Effect.forEach with unbounded concurrency; exported layer and defaultLayer for composition.
InstanceStore Service
packages/opencode/src/project/instance-store.ts
Manages per-directory instance lifecycle with deferred coordination; load reuses cached instances when explicit context matches, reload replaces cached context, dispose coordinates cleanup via State.dispose and disposeInstance; exports LoadInput/Interface contracts and defaultLayer composed with Project.defaultLayer.
Layer Composition and Runtime
packages/opencode/src/project/instance-layer.ts, packages/opencode/src/project/instance-runtime.ts
InstanceLayer composes InstanceStore.defaultLayer with InstanceBootstrap.defaultLayer via Layer.provideMerge; InstanceRuntime wraps Effect operations (load, reload, dispose, disposeAll) in Promise API for caller convenience.
AppLayer Integration
packages/opencode/src/effect/app-runtime.ts
InstanceLayer.layer merged into AppLayer via Layer.provideMerge, making instance lifecycle available throughout Effect environment.
Instance.provide Refactor
packages/opencode/src/project/instance.ts, packages/opencode/src/project/with-instance.ts
Instance.provide delegates to InstanceRuntime.load; directories set tracks loaded instances; containsPath delegated to instance-context helpers; WithInstance.provide helper loads runtime and executes callback within context.
Call Sites Cleanup
packages/opencode/script/seed-e2e.ts, packages/opencode/src/cli/bootstrap.ts, packages/opencode/src/server/instance/middleware.ts, packages/opencode/src/server/instance/project.ts, packages/opencode/src/server/routes/instance/middleware.ts, packages/opencode/src/worktree/index.ts
All Instance.provide and Instance.reload call sites updated to remove explicit init: InstanceBootstrap argument; bootstrap now implicit via InstanceStore.
Service Context Updates
packages/opencode/src/file/watcher.ts, packages/opencode/src/tool/bash.ts, packages/opencode/src/effect/run-service.ts
FileWatcher and BashTool refactored to accept instance context parameter; run-service defensively resolves instance/workspace via fiber context fallback when LocalContext.NotFound.
Instance Lifecycle Tests
packages/opencode/test/project/instance-bootstrap-regression.test.ts, packages/opencode/test/project/instance-store.test.ts, packages/opencode/test/fixture/fixture.ts
Test fixtures and regression suites verify bootstrap execution order across WithInstance.provide, CLI bootstrap, InstanceMiddleware, and InstanceRuntime.reloadInstance; InstanceStore tests cover deduplication, caching, reload, failed-load handling, and database rehydration.

CLI Effect-Based Command Integration

Layer / File(s) Summary
CLI Error Types
packages/opencode/src/cli/effect-cmd.ts, packages/opencode/src/cli/error.ts, packages/opencode/src/cli/cmd/cmd.ts
CliError tagged error class with optional exitCode; fail() helper creates failing effects; FormatError extended to detect and format CliError; WithDoubleDash type exported for type reuse.
effectCmd Builder
packages/opencode/src/cli/effect-cmd.ts
New effectCmd wraps yargs commands with Effect handlers; conditionally loads instance via InstanceStore when opts.instance set; disposes store in try/finally; supports both direct AppRuntime execution and instance-provisioned handlers.
Models Command Refactored
packages/opencode/src/cli/cmd/models.ts
ModelsCommand switched from cmd to effectCmd; handler uses Effect.fn generator syntax with Provider.Service injection; error handling via fail() instead of UI.error().
CLI Tests
packages/opencode/test/cli/effect-cmd-instance-als.test.ts, packages/opencode/test/cli/error.test.ts
Test coverage for FormatError handling CliError and effectCmd preserving Instance.current via Effect.promise with nested AppRuntime.runPromise.

Bus Event Fallback for Missing Instance

Layer / File(s) Summary
Bus Publish Fallback
packages/opencode/src/bus/index.ts
publish wrapped in try/catch; detects LocalContext.NotFound for "instance" tag; emits fallback GlobalBus event with directory: "global" and original payload instead of failing.

Configuration Test Updates

Layer / File(s) Summary
Config Tests Refactored
packages/opencode/test/config/config.test.ts, packages/opencode/test/plugin/workspace-adaptor.test.ts
Tests updated to use withRawInstance helper for plugin/config tests and adjusted Instance.provide assertion patterns; workspace adaptor test refactored to verify adaptor removal after disposal and recreation after re-provisioning.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • Astro-Han/pawwork#360: Modifies Instance.provide in instance.ts and introduces Instance.activate with worktree/project overrides in the same lifecycle management refactoring scope.
  • Astro-Han/pawwork#504: Modifies BashTool's path/permission resolution and collect/execute logic in the same tool/bash.ts file.
  • Astro-Han/pawwork#438: Modifies the same server middleware usage of Instance.provide in packages/opencode/src/server/instance/middleware.ts for ensureConfig handling.

Poem

🐰 Bootstrap dances now in Effects,
Layers weave and contexts flow,
No more callbacks—deferred perfects,
Instance stores steal the show.
Fallback whispers when instance sleeps,
A refactored promise the CLI keeps. 🎪

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor(instance): add Effect foundation and effectCmd proof' directly summarizes the main changes: introducing Effect-backed foundation and demonstrating it with a minimal command wrapper proof.
Description check ✅ Passed The description comprehensively covers all required sections: Summary, Why, Related Issue, Human Review Status, Review Focus, Risk Notes, How To Verify, and Checklist with verification steps and all items checked.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/i477-pr5-effect-httpapi

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors instance management by introducing an InstanceStore to handle the lifecycle, caching, and disposal of project instances. It migrates InstanceBootstrap to an Effect-based service and layer, enabling automatic instance loading and removing explicit bootstrap calls across the codebase. The Instance utility now delegates to the new store and runtime. Feedback highlights a resource leak in the InstanceBootstrap layer due to ignored subscription cleanups and notes potential issues with premature instance disposal in the effectCmd utility during concurrent operations.

Comment thread packages/opencode/src/project/bootstrap.ts Outdated
Comment thread packages/opencode/src/cli/effect-cmd.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
packages/opencode/test/config/config.test.ts (1)

108-112: ⚡ Quick win

Use fixture instance helpers instead of introducing withRawInstance

withRawInstance re-implements instance binding (Project.fromDirectory + Instance.restore) in test code. Please route these tests through provideInstance(...) / provideTmpdirInstance(...) from fixture/fixture.ts to avoid drift as instance lifecycle wiring evolves.

As per coding guidelines, "Prefer Effect-aware helpers from fixture/fixture.ts ... use provideInstance(dir)(effect) ... or provideTmpdirInstance(...)" and "When a test needs instance-local state, prefer provideTmpdirInstance(...) or provideInstance(...) ..."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/test/config/config.test.ts` around lines 108 - 112, The
test introduces a local helper withRawInstance that re-implements
Project.fromDirectory + Instance.restore; replace uses of withRawInstance by
wiring tests through the existing fixture helpers provideInstance(...) or
provideTmpdirInstance(...) from fixture/fixture.ts instead: call
provideInstance(directory)(effect) or provideTmpdirInstance(...) to obtain the
bound Instance instead of manually calling Project.fromDirectory and
Instance.restore, and remove the withRawInstance helper entirely so instance
lifecycle follows the centralized fixture implementation.
packages/opencode/src/project/instance-layer.ts (1)

4-8: 💤 Low value

Dynamic import via Effect.promise will surface load failures as defects.

Effect.promise(() => import("./bootstrap")) treats any rejection as a defect (fiber dies) rather than a typed error. For an internal module import this is usually acceptable, but if you want a clean error path consider Effect.tryPromise with a typed error or a Layer.suspend(() => ...) wrapper. Otherwise the lazy-load shape looks correct and is a reasonable way to break the eager dependency chain into bootstrap.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/project/instance-layer.ts` around lines 4 - 8, The
dynamic import uses Effect.promise which surfaces import failures as defects;
change the bootstrapLayer construction to use a safe effect (e.g.,
Effect.tryPromise with a typed error or wrap the import in Layer.suspend) so
import rejections become handled errors instead of defects. Locate
bootstrapLayer (currently built from Effect.promise(() =>
import("./bootstrap").then(...)) and replace the Effect.promise call with
Effect.tryPromise or Layer.suspend around the import, preserving the extraction
of InstanceBootstrap.defaultLayer and the final composition with
InstanceStore.defaultLayer into layer via Layer.provideMerge.
packages/opencode/src/project/with-instance.ts (1)

4-7: 💤 Low value

Consider clarifying lifecycle ownership of the loaded InstanceRuntime.

provide loads an instance via InstanceRuntime.load(...) but never disposes it on its own — it relies on the centralized InstanceStore lifecycle (e.g., disposeAllInstances()). That's fine for tests and short-lived calls, but a brief comment here would prevent callers from assuming provide is fully scoped/cleanup-safe and reaching for it as a long-running boundary.

Also the typing fn: () => R accepts both sync and async callbacks, which is correct given AsyncLocalStorage propagates across awaits, but you may want to constrain or document the expectation that fn should not outlive the returned promise (e.g., no unawaited fire-and-forget work) since context restoration depends on the await chain.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/project/with-instance.ts` around lines 4 - 7, Add a
short doc comment above provide explaining that the InstanceRuntime returned by
InstanceRuntime.load is owned/managed by the global InstanceStore (e.g., cleaned
up via InstanceStore.disposeAllInstances) so provide does not dispose the
instance itself; also note that context.provide relies on AsyncLocalStorage
propagation so the callback fn passed to provide (the provide function
signature) may be sync or async but must not spawn unawaited background work
that outlives the returned promise. While here you can keep the current
signature of provide(fn: () => R), consider making the signature explicitly
allow Promise returns (fn: () => R | Promise<R>) or add a typed comment
indicating callers must await the promise to retain context.
packages/opencode/test/cli/effect-cmd-instance-als.test.ts (1)

12-35: 💤 Low value

Consider catching/awaiting handler failures explicitly for clearer test diagnostics.

The expect(...) runs inside the Effect.promise callback. If the assertion fails, the rejection propagates through Effect.promise → AppRuntime.runPromise → handler, but Effect's failure wrapping can obscure the original Jest-style error frame, making diagnosis harder. Not a correctness issue, just an observability nit. Otherwise the test cleanly exercises the AsyncLocalStorage bridging path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/test/cli/effect-cmd-instance-als.test.ts` around lines 12 -
35, The test places the Jest assertion inside Effect.promise which can obscure
assertion failures; update the test so the effect handler's returned promise is
awaited and any rejection is rethrown to surface original Jest errors: call and
await the effectCmd handler (the handler created in effectCmd in the "effectCmd
preserves Instance.current..." test), capture errors from
Effect.promise/AppRuntime.runPromise and rethrow them (or fail the test
explicitly) so assertion stack traces from inside Effect.promise are preserved
for Jest diagnostics.
packages/opencode/src/cli/cmd/models.ts (1)

30-33: 💤 Low value

Consider surfacing refresh failures as CliError instead of defects.

Effect.promise(...) turns any ModelsDev.refresh rejection into a Die, which bypasses the new FormatError/CliError formatter and prints a raw stack to the user. Wrapping with Effect.tryPromise + Effect.mapError to fail(...) keeps user-facing CLI errors uniform.

♻️ Proposed refactor
     if (args.refresh) {
-      yield* Effect.promise(() => ModelsDev.refresh(true))
+      yield* Effect.tryPromise({
+        try: () => ModelsDev.refresh(true),
+        catch: (e) => new CliError({ message: `Failed to refresh models cache: ${String(e)}` }),
+      })
       UI.println(UI.Style.TEXT_SUCCESS_BOLD + "Models cache refreshed" + UI.Style.TEXT_NORMAL)
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/cli/cmd/models.ts` around lines 30 - 33, The current
use of Effect.promise when calling ModelsDev.refresh (inside the args.refresh
branch) converts rejections into defects; replace it with Effect.tryPromise
calling ModelsDev.refresh and then use Effect.mapError to convert the rejection
into a CLI-friendly failure (e.g., fail(new CliError(...) or
FormatError-wrapping) so the error flows through the CLI formatter rather than
printing a raw stack; update the call site where Effect.promise(() =>
ModelsDev.refresh(true)) appears to use Effect.tryPromise(() =>
ModelsDev.refresh(true)) and then Effect.mapError to produce fail(...) with a
descriptive CliError.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/opencode/src/effect/run-service.ts`:
- Around line 27-40: WorkspaceContext.workspaceID is read eagerly and can throw
LocalContext.NotFound before the fiber fallback runs; change how workspace is
obtained to mirror the instance lookup: replace the top-level const workspace =
WorkspaceContext.workspaceID with a safe getter that catches
LocalContext.NotFound (like the instance IIFE), e.g., compute workspace using an
IIFE that returns WorkspaceContext.workspaceID inside a try/catch and rethrows
non-LocalContext.NotFound errors, then keep the existing attachWith(effect, {
instance: ..., workspace: ... }) fallback using Fiber.getCurrent and
Context.getReferenceUnsafe(…, WorkspaceRef).

In `@packages/opencode/src/file/watcher.ts`:
- Around line 98-106: The watcher callback cb currently swallows errors by
simply returning inside Instance.restore, which hides runtime failures; modify
the cb in file/watcher.ts so that when the native watcher passes an error (the
err parameter) you log it before returning (e.g., call log.error("watcher
callback error", { err })) and then return; keep the existing Instance.restore
wrapping and the existing event handling (Bus.publish with Event.Updated)
unchanged aside from adding the error log.

In `@packages/opencode/src/project/bootstrap-service.ts`:
- Around line 3-7: The Interface.run is declared as a never-fail Effect but the
implementation calls plugin.init() and config.get() directly (via yield*), which
can throw; update the bootstrap implementation so both calls are wrapped with
the existing boot helper (e.g., replace yield* plugin.init() with yield*
boot(plugin.init()) and yield* config.get() with yield* boot(config.get())) to
preserve the never-fail contract of Service/Interface.run; alternatively, if you
intend failures to be fatal, change the Interface.run type instead—prefer
wrapping with boot to match the other init calls.

In `@packages/opencode/src/project/bootstrap.ts`:
- Around line 49-72: The bootstrap sequence inconsistently calls plugin.init()
and config.get() directly instead of via the error-catching wrapper boot(...);
update the code to call boot(plugin.init()) and boot(config.get()) (or
explicitly document why these must hard-fail) so they get the same
logged-and-continue behavior as shareNext.init(), format.init(), lsp.init(),
file.init(), fileWatcher.init(), vcs.init(), and snapshot.init(); ensure you use
the existing boot function so any thrown Cause is logged and does not abort the
whole instance.

---

Nitpick comments:
In `@packages/opencode/src/cli/cmd/models.ts`:
- Around line 30-33: The current use of Effect.promise when calling
ModelsDev.refresh (inside the args.refresh branch) converts rejections into
defects; replace it with Effect.tryPromise calling ModelsDev.refresh and then
use Effect.mapError to convert the rejection into a CLI-friendly failure (e.g.,
fail(new CliError(...) or FormatError-wrapping) so the error flows through the
CLI formatter rather than printing a raw stack; update the call site where
Effect.promise(() => ModelsDev.refresh(true)) appears to use
Effect.tryPromise(() => ModelsDev.refresh(true)) and then Effect.mapError to
produce fail(...) with a descriptive CliError.

In `@packages/opencode/src/project/instance-layer.ts`:
- Around line 4-8: The dynamic import uses Effect.promise which surfaces import
failures as defects; change the bootstrapLayer construction to use a safe effect
(e.g., Effect.tryPromise with a typed error or wrap the import in Layer.suspend)
so import rejections become handled errors instead of defects. Locate
bootstrapLayer (currently built from Effect.promise(() =>
import("./bootstrap").then(...)) and replace the Effect.promise call with
Effect.tryPromise or Layer.suspend around the import, preserving the extraction
of InstanceBootstrap.defaultLayer and the final composition with
InstanceStore.defaultLayer into layer via Layer.provideMerge.

In `@packages/opencode/src/project/with-instance.ts`:
- Around line 4-7: Add a short doc comment above provide explaining that the
InstanceRuntime returned by InstanceRuntime.load is owned/managed by the global
InstanceStore (e.g., cleaned up via InstanceStore.disposeAllInstances) so
provide does not dispose the instance itself; also note that context.provide
relies on AsyncLocalStorage propagation so the callback fn passed to provide
(the provide function signature) may be sync or async but must not spawn
unawaited background work that outlives the returned promise. While here you can
keep the current signature of provide(fn: () => R), consider making the
signature explicitly allow Promise returns (fn: () => R | Promise<R>) or add a
typed comment indicating callers must await the promise to retain context.

In `@packages/opencode/test/cli/effect-cmd-instance-als.test.ts`:
- Around line 12-35: The test places the Jest assertion inside Effect.promise
which can obscure assertion failures; update the test so the effect handler's
returned promise is awaited and any rejection is rethrown to surface original
Jest errors: call and await the effectCmd handler (the handler created in
effectCmd in the "effectCmd preserves Instance.current..." test), capture errors
from Effect.promise/AppRuntime.runPromise and rethrow them (or fail the test
explicitly) so assertion stack traces from inside Effect.promise are preserved
for Jest diagnostics.

In `@packages/opencode/test/config/config.test.ts`:
- Around line 108-112: The test introduces a local helper withRawInstance that
re-implements Project.fromDirectory + Instance.restore; replace uses of
withRawInstance by wiring tests through the existing fixture helpers
provideInstance(...) or provideTmpdirInstance(...) from fixture/fixture.ts
instead: call provideInstance(directory)(effect) or provideTmpdirInstance(...)
to obtain the bound Instance instead of manually calling Project.fromDirectory
and Instance.restore, and remove the withRawInstance helper entirely so instance
lifecycle follows the centralized fixture implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f189f9f-e458-4fb6-a12c-c11511e41875

📥 Commits

Reviewing files that changed from the base of the PR and between 9f2275e and 8e46a0f.

📒 Files selected for processing (30)
  • packages/opencode/script/seed-e2e.ts
  • packages/opencode/src/bus/index.ts
  • packages/opencode/src/cli/bootstrap.ts
  • packages/opencode/src/cli/cmd/cmd.ts
  • packages/opencode/src/cli/cmd/models.ts
  • packages/opencode/src/cli/effect-cmd.ts
  • packages/opencode/src/cli/error.ts
  • packages/opencode/src/effect/app-runtime.ts
  • packages/opencode/src/effect/run-service.ts
  • packages/opencode/src/file/watcher.ts
  • packages/opencode/src/project/bootstrap-service.ts
  • packages/opencode/src/project/bootstrap.ts
  • packages/opencode/src/project/instance-context.ts
  • packages/opencode/src/project/instance-layer.ts
  • packages/opencode/src/project/instance-runtime.ts
  • packages/opencode/src/project/instance-store.ts
  • packages/opencode/src/project/instance.ts
  • packages/opencode/src/project/with-instance.ts
  • packages/opencode/src/server/instance/middleware.ts
  • packages/opencode/src/server/instance/project.ts
  • packages/opencode/src/server/routes/instance/middleware.ts
  • packages/opencode/src/tool/bash.ts
  • packages/opencode/src/worktree/index.ts
  • packages/opencode/test/cli/effect-cmd-instance-als.test.ts
  • packages/opencode/test/cli/error.test.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/test/fixture/fixture.ts
  • packages/opencode/test/plugin/workspace-adaptor.test.ts
  • packages/opencode/test/project/instance-bootstrap-regression.test.ts
  • packages/opencode/test/project/instance-store.test.ts
💤 Files with no reviewable changes (6)
  • packages/opencode/src/server/routes/instance/middleware.ts
  • packages/opencode/src/cli/bootstrap.ts
  • packages/opencode/src/server/instance/middleware.ts
  • packages/opencode/src/worktree/index.ts
  • packages/opencode/script/seed-e2e.ts
  • packages/opencode/src/server/instance/project.ts

Comment thread packages/opencode/src/effect/run-service.ts Outdated
Comment thread packages/opencode/src/file/watcher.ts
Comment thread packages/opencode/src/project/bootstrap-service.ts
Comment thread packages/opencode/src/project/bootstrap.ts
@Astro-Han Astro-Han merged commit 769998a into dev May 9, 2026
20 checks passed
@Astro-Han Astro-Han deleted the codex/i477-pr5-effect-httpapi branch May 9, 2026 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P1 High priority task Maintainer or agent execution task upstream Tracked upstream or vendor behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant