This reverts: -28b03595bresearch: delete Hono backend (do not merge) (#25667) -b24a4e897chore(server): clean up post-Hono-deletion scar tissue (#26542) v1.14.42 broke startup for users with plugins that depend on the Hono wire format (most visibly opencode-gemini-auth, see #26546). Restoring Hono as the default backend on stable channels while we investigate the actual plugin compatibility story. OPENCODE_EXPERIMENTAL_HTTPAPI flag and dual-backend selection come back. Stable installs default to Hono; dev/beta default to HTTP API. Conflict resolution: took the pre-deletion side for control-plane schemas and the seven test files where post-deletion follow-up PRs had also touched the conflicting lines. The HTTP API code added since the deletion (compression, cors-vary, fence, lifecycle log, account error mapping, etc.) is preserved as-is — those still apply on the HTTP API path for users on dev/beta channels.
24 KiB
HttpApi migration
Plan for replacing instance Hono route implementations with Effect HttpApi while preserving behavior, OpenAPI, and SDK output during the transition.
End State
- JSON route contracts and handlers live in
src/server/routes/instance/httpapi/*. - Route modules own their
HttpApiGroup, schemas, handlers, and route-level middleware. httpapi/server.tsonly composes groups, instance lookup, observability, and the web handler bridge.- Hono route implementations are deleted once their
HttpApireplacements are default, tested, and represented in the SDK/OpenAPI pipeline. - Streaming, SSE, and websocket routes move later through Effect HTTP primitives or another explicit replacement plan; they do not need to fit
HttpApiifHttpApiis the wrong abstraction.
Current State
OPENCODE_EXPERIMENTAL_HTTPAPIselects the backend at server startup. Default is stillhono.server/backend.tspicks one ofeffect-httpapiorhono;server.tsbuilds either a pure EffectHttpApiweb handler or the legacy Hono app accordingly. The earlier in-Hono "bridge" model has been replaced by this fork-at-startup.- Legacy Hono routes remain mounted for the
honobackend and remain the source forhono-openapiSDK generation. - An Effect
HttpApiOpenAPI surface exists (OpenApi.fromApi(PublicApi)incli/cmd/generate.ts --httpapi,OPENCODE_SDK_OPENAPI=httpapiinpackages/sdk/js/script/build.ts) but is opt-in. The default SDK generation is still Hono. httpapi/public.tscarries the Hono-compat normalization for the Effect-generated OpenAPI surface (auth scheme strip, request-body required flag, optionalnullarms,BadRequestError/NotFoundErrorremap,$refself-cycle fix,auth_tokenquery injection). Today's Effect-generated SDK is not byte-identical to the Hono-generated SDK — see Phase 4.- Auth is centrally configured for the Effect backend via Effect
Config(refactor: use Effect config for HttpApi authorization,Fix HttpApi raw route authorization) rather than re-attached in each route module. - Auth supports Basic auth and the legacy
auth_tokenquery parameter throughHttpApiSecurity.apiKey. - Instance context is provided by
httpapi/server.tsusingdirectory,workspace, andx-opencode-directory. Observability.layeris provided in the Effect route layer and deduplicated through the sharedmemoMap.- CORS middleware is wired into both backends (
feat(httpapi): add CORS middleware to instance routes).
Migration Rules
- Preserve runtime behavior first. Semantic changes, new error behavior, or route shape changes need separate PRs.
- Migrate one route group, or one coherent subset of a route group, at a time.
- Reuse existing services. Do not re-architect service logic during HTTP boundary migration.
- Effect Schema owns route DTOs. Keep
.zodonly as compatibility for remaining Hono/OpenAPI surfaces. - Regenerate the SDK after schema or OpenAPI-affecting changes and verify the diff is expected.
- Do not delete a Hono route until the SDK/OpenAPI pipeline no longer depends on its Hono
describeRouteentry.
Route Slice Checklist
Use this checklist for each small HttpApi migration PR:
- Read the legacy Hono route and copy behavior exactly, including default values, headers, operation IDs, response schemas, and status codes.
- Put the new
HttpApiGroup, route paths, DTO schemas, and handlers insrc/server/routes/instance/httpapi/*. - Mount the new paths in
src/server/routes/instance/index.tsonly inside theOPENCODE_EXPERIMENTAL_HTTPAPIblock. - Use
InstanceState.context/InstanceState.directoryinside HttpApi handlers instead ofInstance.directory,Instance.worktree, orInstance.projectALS globals. - Reuse existing services directly. If a service returns plain objects, use
Schema.Struct; useSchema.Classonly when handlers return actual class instances. - Keep legacy Hono routes and
.zodcompatibility in place for SDK/OpenAPI generation. - Add tests that hit the Hono-mounted bridge via
InstanceRoutes, not only the rawHttpApiweb handler, when the route depends on auth or instance context. - Run
bun typecheckfrompackages/opencode, relevantbun run test:ci ...tests frompackages/opencode, and./packages/sdk/js/script/build.tsfrom the repo root.
Hono Deletion Checklist
Use this checklist before deleting any Hono route implementation. A route being bridged is not enough.
HttpApiparity is complete for the route path, method, auth behavior, query parameters, request body, response status, response headers, and error status.- The route is mounted by default, not only behind
OPENCODE_EXPERIMENTAL_HTTPAPI. - If a fallback flag exists, tests cover both the default
HttpApipath and the fallback Hono path until the fallback is removed. - OpenAPI generation uses the Effect
HttpApiroute as the source for that path. - Generated SDK output is unchanged from the Hono-generated contract, or the SDK diff is intentionally reviewed and accepted.
- The legacy Hono
describeRoute, validator, and handler for that path are removed. - Any duplicate Zod-only DTOs are deleted or kept only as
.zodcompatibility on the canonical Effect Schema. - Bridge tests exist for auth, instance selection, success response, and route-specific side effects.
- Mutation routes prove persisted side effects and cleanup behavior in tests. If the mutation disposes/reloads the active instance, disposal happens through an explicit post-response lifecycle hook rather than inline handler teardown.
- Streaming, SSE, websocket, and UI bridge routes have a specific non-Hono replacement plan. Do not force them through
HttpApiif raw Effect HTTP is a better fit.
Hono can be removed from the instance server only after all mounted Hono route groups meet this checklist and server/routes/instance/index.ts no longer depends on Hono routing for default behavior.
Experimental Read Slice Guidance
For the experimental route group, port read-only JSON routes before mutations:
- Good first batch:
GET /console,GET /console/orgs,GET /tool/ids,GET /resource. - Consider
GET /worktreeonly if the handler usesInstanceState.contextinstead ofInstance.project. - Defer
POST /console/switch, worktree create/remove/reset, andGET /sessionto separate PRs because they mutate state or have broader pagination/session behavior. - Preserve response headers such as pagination cursors if a route is ported.
- If SDK generation changes, explain whether it is a semantic contract change or a generator-equivalent type normalization.
Schema Notes
- Use
Schema.Struct(...).annotate({ identifier })for named OpenAPI refs when handlers return plain objects. - Use
Schema.Classonly when the handler returns real class instances or the constructor requirement is intentional. - Keep nested anonymous shapes as
Schema.Structunless a named SDK type is useful. - Avoid parallel hand-written Zod and Effect definitions for the same route boundary.
Phases
1. Stabilize The Bridge
Before porting more routes, cover the bridge behavior that every route depends on.
- Add tests that hit the Hono-mounted
HttpApibridge, not justHttpApiBuilder.layerdirectly. - Cover auth disabled, Basic auth success,
auth_tokensuccess, missing credentials, and bad credentials. - Cover
directoryandx-opencode-directoryinstance selection. - Verify generated SDK output remains unchanged for non-SDK work.
- Fix or remove any implemented-but-unmounted
HttpApigroups.
2. Complete The Inventory
Create a route inventory from the actual Hono registrations and classify each route.
Statuses:
bridged: served through theHttpApibridge when the flag is on.implemented:HttpApigroup exists but is not mounted through Hono.next: good JSON candidate for near-term porting.later: portable, but needs schema/service cleanup first.special: SSE, websocket, streaming, or UI bridge behavior that likely needs raw Effect HTTP rather thanHttpApi.
3. Finish JSON Route Parity
Port remaining JSON routes in small batches.
Good near-term candidates:
- top-level reads:
GET /path,GET /vcs,GET /vcs/diff,GET /command,GET /agent,GET /skill,GET /lsp,GET /formatter - simple mutations:
POST /instance/dispose - experimental JSON reads: console, tool, worktree list, resource list
- deferred JSON mutations: workspace/worktree create/remove/reset, file search, MCP auth flows
Keep large or stateful groups for later:
sessionsync- process-level experimental routes
4. Move OpenAPI And SDK Generation
Hono routes cannot be deleted while hono-openapi is the source of SDK generation.
Status: the Effect HttpApi OpenAPI surface is implemented and opt-in (bun dev generate --httpapi, OPENCODE_SDK_OPENAPI=httpapi). Default SDK generation still uses Hono. httpapi/public.ts applies the Hono-compat normalization layer to the Effect output. Diff against the Hono-generated spec still shows real gaps that must be closed before the SDK can flip:
- Branded-type
patternconstraints on ID schemas are not propagated to the Effect output (~169 missing). - Per-property
descriptionannotations are not propagated throughSchema.Structto the Effect output (~107 missing). Event.*andSyncEvent.*component names use dotted form in Hono and PascalCase in Effect (~50 differences, breaks SDK type names).- Effect's component deduper emits numbered duplicates (
Session9,SyncEvent.session.updated.11) that need a name-collision fix. - Cosmetic-only diffs (
additionalProperties: false,constvsenum, MAX_SAFE_INTEGERmaximum,propertyNames) can be normalized inpublic.tsif they would otherwise change SDK output.
Required before route deletion:
- Close the diff above so Effect-generated SDK output matches the Hono-generated SDK output for every retained path.
- Keep operation IDs, schemas, status codes, and SDK type names stable unless the change is intentional.
- Flip
packages/sdk/js/script/build.tsdefault tohttpapiand regenerate. - Compare generated SDK output against
devfor every route group deletion. - Remove Hono OpenAPI stubs only after Effect OpenAPI is the SDK source for those paths.
V2 cleanup once SDK compatibility no longer needs the legacy Hono contract:
- Remove
public.tscompatibility transforms that hide honestHttpApimetadata, including authsecuritySchemes, per-routesecurity, and generated401responses. - Stop remapping built-in
HttpApierror schemas back to legacy HonoBadRequestError/NotFoundErrorcomponents if V2 clients can consume the actual Effect error shape. - Prefer the direct
HttpApiOpenAPI output for request/response bodies and named component schemas instead of rewriting it to match Hono generator quirks. - Keep schema fixes that describe the actual wire format, but delete transforms that only preserve legacy SDK type names or inline-vs-ref shape.
- Re-evaluate
auth_tokenas an OpenAPI security scheme rather than a hand-injected query parameter once clients can consume the V2 spec.
5. Make HttpApi Default For JSON Routes
After JSON parity and SDK generation are covered:
- Flip the bridge default for ported JSON routes.
- Keep a short-lived fallback flag for the old Hono implementation.
- Run the same tests against both the default and fallback path during rollout.
- Stop adding new Hono handlers for JSON routes once the default flips.
6. Delete Hono Route Implementations
Delete Hono routes group-by-group after each group meets the deletion criteria.
Deletion criteria:
HttpApiroute is mounted by default.- Behavior is covered by bridge-level tests.
- OpenAPI/SDK generation comes from Effect for that path.
- SDK diff is zero or explicitly accepted.
- Legacy Hono route is no longer needed as a fallback.
After deleting a group:
- Remove its Hono route file or dead endpoints.
- Remove its
.route(...)registration frominstance/index.ts. - Remove duplicate Zod-only route DTOs if Effect Schema now owns the type.
- Regenerate SDK and verify output.
7. Replace Special Routes
Special routes need explicit designs before Hono can disappear completely.
event: SSEpty: websockettui: UI/control bridge behavior- streaming
sessionendpoints
Use raw Effect HTTP routes where HttpApi does not fit. The goal is deleting Hono implementations, not forcing every transport shape through HttpApi.
Current Route Status
| Area | Status | Notes |
|---|---|---|
question |
bridged |
GET /question, reply, reject |
permission |
bridged |
list and reply |
provider |
bridged |
list, auth, OAuth authorize/callback |
config |
bridged |
read, providers, update |
project |
bridged |
list, current, git init, update |
file |
bridged partial |
find text/file/symbol, list/content/status |
mcp |
bridged |
status, add, OAuth, connect/disconnect |
workspace |
bridged |
adapter/list/status/create/remove/session-restore |
| top-level instance routes | bridged |
path, vcs, command, agent, skill, lsp, formatter, dispose |
| experimental JSON routes | bridged |
console, tool, worktree list/mutations, global session list, resource list |
session |
bridged |
read, lifecycle, prompt, message/part mutations, revert, permission reply |
sync |
bridged |
start/replay/history |
event |
bridged |
SSE via raw Effect HTTP |
pty |
special |
websocket |
tui |
special |
UI bridge |
Full Route Checklist
This checklist tracks bridge parity only. Checked routes are available through the experimental HttpApi bridge; Hono deletion is tracked separately by the deletion checklist above.
Top-Level Instance Routes
POST /instance/dispose- dispose active instance after response.GET /path- current directory and worktree paths.GET /vcs- current VCS status.GET /vcs/diff- VCS diff summary.GET /command- command catalog.GET /agent- agent catalog.GET /skill- skill catalog.GET /lsp- LSP status.GET /formatter- formatter status.
Config Routes
GET /config- read config.PATCH /config- update config and dispose active instance after response.GET /config/providers- config provider summary.
Project Routes
GET /project- list projects.GET /project/current- current project.POST /project/git/init- initialize git and reload active instance after response.PATCH /project/:projectID- update project metadata.
Provider Routes
GET /provider- list providers.GET /provider/auth- list provider auth methods.POST /provider/:providerID/oauth/authorize- start provider OAuth.POST /provider/:providerID/oauth/callback- finish provider OAuth.
Question Routes
GET /question- list questions.POST /question/:requestID/reply- reply to question.POST /question/:requestID/reject- reject question.
Permission Routes
GET /permission- list permission requests.POST /permission/:requestID/reply- reply to permission request.
File Routes
GET /find- text search.GET /find/file- file search.GET /find/symbol- symbol search.GET /file- list directory entries.GET /file/content- read file content.GET /file/status- file status.
MCP Routes
GET /mcp- MCP status.POST /mcp- add MCP server at runtime.POST /mcp/:name/auth- start MCP OAuth.POST /mcp/:name/auth/callback- finish MCP OAuth callback.POST /mcp/:name/auth/authenticate- run MCP OAuth authenticate flow.DELETE /mcp/:name/auth- remove MCP OAuth credentials.POST /mcp/:name/connect- connect MCP server.POST /mcp/:name/disconnect- disconnect MCP server.
Experimental Routes
GET /experimental/console- active Console provider metadata.GET /experimental/console/orgs- switchable Console orgs.POST /experimental/console/switch- switch active Console org.GET /experimental/tool/ids- tool IDs.GET /experimental/tool- tools for provider/model.GET /experimental/worktree- list worktrees.POST /experimental/worktree- create worktree.DELETE /experimental/worktree- remove worktree.POST /experimental/worktree/reset- reset worktree.GET /experimental/session- global session list.GET /experimental/resource- MCP resources.
Workspace Routes
GET /experimental/workspace/adapter- list workspace adapters.POST /experimental/workspace- create workspace.GET /experimental/workspace- list workspaces.GET /experimental/workspace/status- workspace status.DELETE /experimental/workspace/:id- remove workspace.POST /experimental/workspace/:id/session-restore- restore session into workspace.
Sync Routes
POST /sync/start- start workspace sync.POST /sync/replay- replay sync events.POST /sync/history- list sync event history.
Session Routes
GET /session- list sessions.GET /session/status- session status map.GET /session/:sessionID- get session.GET /session/:sessionID/children- get child sessions.GET /session/:sessionID/todo- get session todos.POST /session- create session.DELETE /session/:sessionID- delete session.PATCH /session/:sessionID- update session metadata.POST /session/:sessionID/init- run project init command.POST /session/:sessionID/fork- fork session.POST /session/:sessionID/abort- abort session.POST /session/:sessionID/share- share session.GET /session/:sessionID/diff- session diff.DELETE /session/:sessionID/share- unshare session.POST /session/:sessionID/summarize- summarize session.GET /session/:sessionID/message- list session messages.GET /session/:sessionID/message/:messageID- get message.DELETE /session/:sessionID/message/:messageID- delete message.DELETE /session/:sessionID/message/:messageID/part/:partID- delete part.PATCH /session/:sessionID/message/:messageID/part/:partID- update part.POST /session/:sessionID/message- prompt with streaming response.POST /session/:sessionID/prompt_async- async prompt.POST /session/:sessionID/command- run command.POST /session/:sessionID/shell- run shell command.POST /session/:sessionID/revert- revert message.POST /session/:sessionID/unrevert- restore reverted messages.POST /session/:sessionID/permissions/:permissionID- deprecated permission response route.
Event Routes
GET /event- SSE event stream via raw Effect HTTP.
PTY Routes
GET /pty- list PTY sessions.POST /pty- create PTY session.GET /pty/:ptyID- get PTY session.PUT /pty/:ptyID- update PTY session.DELETE /pty/:ptyID- remove PTY session.GET /pty/:ptyID/connect- PTY websocket; replace with raw Effect HTTP/websocket support.
TUI Routes
POST /tui/append-prompt- append prompt.POST /tui/open-help- open help.POST /tui/open-sessions- open sessions.POST /tui/open-themes- open themes.POST /tui/open-models- open models.POST /tui/submit-prompt- submit prompt.POST /tui/clear-prompt- clear prompt.POST /tui/execute-command- execute command.POST /tui/show-toast- show toast.POST /tui/publish- publish TUI event.POST /tui/select-session- select session.GET /tui/control/next- get next TUI request.POST /tui/control/response- submit TUI control response.
Remaining PR Plan
Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays reviewable.
- Bridge
PATCH /project/:projectID. - Bridge MCP add/connect/disconnect routes.
- Bridge MCP OAuth routes: start, callback, authenticate, remove.
- Bridge experimental console switch and tool list routes.
- Bridge experimental global session list.
- Bridge workspace create/remove/session-restore routes.
- Bridge sync start/replay/history routes.
- Bridge session read routes: list, status, get, children, todo, diff, messages.
- Bridge session lifecycle mutation routes: create, delete, update, fork, abort.
- Bridge remaining session mutation and prompt routes.
- Replace event SSE with non-Hono Effect HTTP. The Effect backend has a raw Effect HTTP
httpapi/event.ts; the Hono backend still useshono/streamingstreamSSE. Either port Hono/eventto raw Effect HTTP for the fallback window, or skip and delete it together with Hono in step 15. - Replace pty websocket/control routes with non-Hono Effect HTTP for the Effect backend. Hono
pty.tsremains in the Hono backend. - Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer for the Effect backend. Hono
tui.tsremains in the Hono backend. - Switch OpenAPI/SDK generation to Effect routes and compare SDK output. Effect path is implemented and opt-in via
--httpapi/OPENCODE_SDK_OPENAPI=httpapi. Close the schema-shape gaps inpublic.ts(brandedpattern, per-propertydescription,Event.*/SyncEvent.*naming, dedup collisions), then flippackages/sdk/js/script/build.tsdefault. - Flip
backend.tsdefault fromhonotoeffect-httpapi, keepOPENCODE_EXPERIMENTAL_HTTPAPI(or its inverse) as a short fallback flag, then delete replaced Hono route files.
Checklist
- Add first
HttpApiJSON route slices. - Bridge selected
HttpApiroutes behindOPENCODE_EXPERIMENTAL_HTTPAPI. (Now backend-fork-at-startup rather than in-Hono path mounting.) - Reuse existing Effect services in handlers.
- Provide auth, instance lookup, and observability in the Effect route layer.
- Centralize auth via Effect
Configfor the Effect backend. - Support
auth_tokenas a query security scheme. - Add bridge-level auth and instance tests.
- Complete exact Hono route inventory.
- Resolve implemented-but-unmounted route groups.
- Port remaining top-level JSON reads.
- Implement Effect
HttpApiOpenAPI generation behind--httpapi/OPENCODE_SDK_OPENAPI=httpapi. - Close Effect-vs-Hono OpenAPI schema-shape gaps and flip the SDK generator default.
- Flip the runtime backend default from
honotoeffect-httpapi, with a short fallback flag. - Delete replaced Hono route implementations.
- Replace SSE/websocket/streaming Hono routes with non-Hono implementations (or remove with the rest of Hono).