38. No auto-detect/re-exec: a project's custom binary is invoked explicitly, via a generated wrapper
- Status: Accepted
- Date: 2026-09-05
Context
ADR 0037 settled that a project depending
on a native module gets its own custom-built korrin binary, distinct
from whatever generic korrin is installed on $PATH. That leaves one
question genuinely open: how does a developer actually end up running the
right one?
Two real designs, and this is the one place in Milestone 6's design this project's own plan was genuinely torn on.
Decision
No magic. korrin pkg build prints the custom binary's path on success
and writes a small, gitignored, project-root wrapper script (./korrin
on Unix, ./korrin.cmd on Windows) that forwards argv to it. The
generic korrin run needs no new mechanism for the failure path at
all — it already, today, raises the ordinary E0314 ImportError the
moment a program imports a name only a native-module registry (which the
generic binary was never given) would satisfy.
Consequences
- A developer working on a project with a native-module dependency must
remember to invoke the generated wrapper (or the custom binary directly)
instead of whatever generic
korrinhappens to be on$PATH. This is a real, accepted cost — directly comparable to a C project needing its own custom-built toolchain rather than the system compiler — named plainly here rather than hidden behind implicit re-exec magic. - Running
korrin run game.korwith the wrong (generic) binary fails loudly and immediately with a diagnostic already fully explained by existing behavior (E0314, "no built-in module and no installed package/native module named ..."), not silently or confusingly. - No platform-specific process-spawning code is needed for this milestone
— a real re-exec (spawning a child, forwarding stdio and exit code by
hand, since Windows has no
execve) is deferred until, if ever, it's actually wanted.
Alternatives considered
- Auto-detect and re-exec. The generic
korrinbinary notices a project has a native-module dependency, checks a freshness marker, and transparently spawns the project's custom binary in its place. Rejected: it needs real, fiddly, platform-specific process-spawning code (Windows has noexecve) for a first cut that doesn't need it, and it is exactly the kind of implicit magic this project has already rejected once on identical grounds — ADR 0019 refused a filesystem probe forimportresolution specifically because "an import line's meaning would depend on which files exist." Which binary runs a program is at least as consequential a question as which module an import resolves to, and deserves no less explicitness. A silent delegation to a separately-built, possibly-stale, possibly differently-versioned binary is also a materially worse debugging experience than a loud, immediate failure.