Skip to content

Bun runtime port — fact-checked pros/cons, and the OpenTUI gate #27

Description

@srid

Question

Should odu move from node+tsx to bun, the way srid/drishti did — primarily to unlock OpenTUI for the attach face?

This issue records the fact-checked research (2026-06-12) so the decision has a paper trail.

Where the runtime choice actually lives

odu on node is packaging path-of-least-resistance, not a considered runtime decision:

  • default.nix fetches deps with nixpkgs-native fetchPnpmDeps + pnpmConfigHook and ships odu / odu-runner as makeWrapper-ed tsx scripts — no bundle step, zero flake inputs.
  • drishti had to break the zero-input convention to use bun: its flake pulls juspay/bun2nix (rawflake) as the documented exception, because nixpkgs has no fetchBunDeps / buildBunPackage. That pattern is directly copyable.

The roadmap already gates this — kolu#1294

The merged juspay/kolu#1294 atlas notes evaluated OpenTUI empirically against 0.4.0:

  • Packaging is a non-issue: @opentui/core ships prebuilt natives as optionalDependencies for all four kolu systems; fetchPnpmDeps ingests them like esbuild's binary.
  • The real gate is the runtime: under node 24, createCliRenderer throws "OpenTUI native FFI is not available for this runtime yet" — the package ships libopentui.so for bun:ffi, no napi addon.
  • Plan of record: prototype odu attach on OpenTUI when upstream's node support lands"or earlier under bun for just the attach face." The roadmap sanctions a bun attach face, not a whole-runner port.
  • The pull beyond widgets: @kolu/surface's reactive hooks (useCell/useStream) are Solid-only — today's hand-rolled TUI can't use them. On @opentui/solid the terminal becomes a Solid tree over those hooks, and odu-web Phase 2 plans the browser observer over the same hooks: faces over one surface becomes shared view code.

Fact-check findings

OpenTUI is no longer strictly bun-only. The project moved to anomalyco/opentui; per the official docs, the native renderer now also runs on Node 26.3.0 with --experimental-ffi (the long-open Node/Deno support issue is being answered via Node's experimental FFI rather than napi). But that's current-line Node behind an experimental flag, while nixpkgs' pkgs.nodejs is LTS — so bun is the production-viable path today; node may be the path in 6–12 months.

Bun's child-process layer is the real port risk, but smaller than feared. odu's runner does detached: true + process.kill(-pid) group kills (src/runner/runner.ts); bun now supports detached properly (setsid(), child becomes group leader — oven-sh/bun#1442 resolved). odu's stdio framing uses only fd 0–2, so the long-standing extra-fds gap #4670 doesn't bite. Remaining known issues are edge-case: missing close event on fast exits, intermittent CI hangs. The EPIPE-on-teardown class of bug (#25) is exactly where runtime semantics differ — re-litigation risk, not a known blocker.

Tests: vitest can run under bun via bun run --bun vitest (unofficial); realistic choices are migrating to bun test or keeping vitest on node — with the caveat that node-hosted unit tests wouldn't exercise bun runtime behavior, leaving the e2e suite (#20, driving the nix-built binary) as the only bun-fidelity coverage.

Pros

  • Unlocks OpenTUI now on the proven bun:ffi path, with @opentui/solid slotting onto odu's existing solid-js (^1.9.0 satisfies the 1.9.12 peer-pin) and the surface hooks.
  • Simpler runtime stack: drops tsx and the node wrapper; bun runs the .ts entrypoints natively, with faster startup — relevant since the runner is spawned fresh over ssh per attach.
  • In-house precedent: drishti's bun2nix flake pattern is directly copyable; the Nix cost is already paid once.

Cons

  • Runtime risk lands on odu's core competency — process supervision and stdio teardown — where bun's node-compat history is weakest. Unit tests on node won't catch it; only e2e will.
  • Cross-repo runtime divergence: kolu is pnpm+node (node-pty in kaval), and the surface-daemon plan has odu serve consuming a spine born in kaval under node. A bun odu makes bun a second, upstream-untested runtime for shared @kolu/* code — and that coupling tightens over time.
  • The motivation may have a shelf life: with Node's experimental FFI already running OpenTUI's renderer, a full-runner bun port could be obsolete as a means to OpenTUI once that stabilizes into an LTS.
  • Mechanical churn: bun2nix flake input, bun.lock, adapting scripts/hydrate-kolu-packages.sh to bun's node_modules layout, and a test-runner decision.

Recommendation

Follow the roadmap's own middle path rather than a whole-repo port:

  1. Prototype the attach face on bun + OpenTUI — read-only, smallest blast radius, no process supervision in that code path, so bun's weak spots don't apply. default.nix already builds odu and odu-runner as separate wrappers; a bun-wrapped attach entrypoint is a contained change.
  2. Decide on the full-runner port only after (a) the prototype proves bun out under the e2e suite, or (b) OpenTUI-on-node stabilizes and makes it unnecessary.
  3. Cheap first step either way: re-run the kolu#1294 renderer probe under Node 26.3 --experimental-ffi to see how real that path is today.

Research conducted with Claude Code (claude-fable-5); sources linked inline.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions