Skip to content

feat(cli): add instance: false opt-out to effectCmd#25507

Merged
kitlangton merged 2 commits into
devfrom
kit/cli-effect-cmd-no-instance
May 3, 2026
Merged

feat(cli): add instance: false opt-out to effectCmd#25507
kitlangton merged 2 commits into
devfrom
kit/cli-effect-cmd-no-instance

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

Summary

For commands that don't read project state, the always-on `InstanceStore.provide` adds unnecessary work (`InstanceBootstrap` = config + plugin init + LSP/File/Snapshot/Vcs/Format/ShareNext init forks) and emits a `server.instance.disposed` IPC event the TUI re-bootstraps on.

Add `instance: false` to opt out: handler runs directly under `AppRuntime` with no instance load and no auto-dispose. Default stays `true` (no breaking change for existing converted commands).

```ts
effectCmd({
command: "serve",
instance: false, // ← skip InstanceStore.provide
handler: Effect.fn(...)(...)
})
```

First user: serve.ts

Converted as the demo. Server loads instances per-request via the `x-opencode-directory` header, so it never needed an ambient one — keeping the legacy "no instance load" semantics while still gaining `effectCmd`'s shape.

Future candidates (not in this PR)

`web`, `account` (login/logout/switch/orgs/open/console), `db` (path/query/migrate), `upgrade`, `uninstall` — all currently still on raw `cmd()` because the always-load behavior would add startup work they don't need.

Behavioral notes

  • `models` was considered but cannot opt out: `Provider.list` reads `InstanceState` (per-instance config + plugins + auth), so it genuinely needs the project instance.
  • For `serve`: identical observable behavior (no instance load before; no instance load now). Just the same shape via `effectCmd`.

Test plan

  • `bun run typecheck`
  • `bun run test test/cli/` — 137 pass
  • Smoke: `serve --port 0` starts and listens

For commands that don't read project state (e.g. serve, web, account, db,
upgrade), the always-on InstanceStore.provide adds unnecessary work
(InstanceBootstrap = config + plugin init + LSP/File/etc forks) and
emits a server.instance.disposed IPC event the TUI then re-bootstraps on.

Add `instance: false` to opt out: handler runs directly under AppRuntime
with no instance load, no auto-dispose. Default stays true (no breaking
change for existing converted commands).

Convert serve.ts as the first user — server loads instances per-request
via x-opencode-directory header, so it never needed an ambient one.
Effect.never is the idiomatic block-forever, replacing the
Effect.promise(() => new Promise<void>(() => {})) bridge. The
server.stop() call after it was unreachable in the legacy code too
(await new Promise(() => {}) never resolves), so dropping it.
@kitlangton kitlangton enabled auto-merge (squash) May 3, 2026 01:35
@kitlangton kitlangton merged commit e98c291 into dev May 3, 2026
10 checks passed
@kitlangton kitlangton deleted the kit/cli-effect-cmd-no-instance branch May 3, 2026 01:44
oleksii-honchar pushed a commit to oleksii-honchar/better-opencode that referenced this pull request May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant