Found while triaging a codex finding on PR #568. The compatibility half of that finding does not apply (see below); this is the half that outlives it.
The gap
runsetup.make_adapters builds every adapter class with:
by_cfg[key] = cls(**build_kwargs)
except builder.construct_error as e:
raise SystemExit(f"error: {e}") from e
construct_error is family-declared and covers what a family says its __init__ can raise — () for generic, (OpencodeServerError,) for opencode. A signature mismatch is not that: an out-of-tree class whose __init__ does not accept a keyword core passes raises TypeError, which no family declares, so it escapes.
Two lines up, the kind.load() arm already states why that matters:
By here compose_run has already written the run state and pid, so an escaping ImportError strands a run directory behind a traceback.
The same is true here, and one step later. The ImportError arm was given a clean error: line naming the profile and the kind; the construct call has no equivalent for the mismatch case.
Why it is not urgent
The out-of-tree adapter-class capability (#226, PR #239) has not shipped in a tagged release — the last tag is v0.9.1 and the entry is under ## [Unreleased]. The authoring guide states there is no out-of-tree adapter-class package to copy yet, so today the only classes reaching this line are the bundled ones, which are edited in the same commit as any new keyword.
PR #568 documents the obligation on the author side (accept **kwargs; the bootstrap keyword set grows), which is the cheap half and the one that belongs before first release. This issue is the core-side half: even a correct contract gets violated, and the failure should read like the rest of the bootstrap rather than like a crash.
Shape
Catch TypeError around the construct call and re-raise as SystemExit naming the profile, the kind, and the offending keyword — matching the ImportError arm above it. TypeError only, on the same rule that arm states: a declared failure gets a clean line, anything else is a bug in that package and must surface as itself.
Care needed on one point: a TypeError raised from inside a working __init__ is a bug in that package and must not be relabelled as a signature mismatch. Distinguishing the two means inspecting the exception rather than merely its type — worth pinning with a test that a TypeError thrown from the constructor body still surfaces as itself.
Related: #226, PR #239, PR #568.
Found while triaging a codex finding on PR #568. The compatibility half of that finding does not apply (see below); this is the half that outlives it.
The gap
runsetup.make_adaptersbuilds every adapter class with:construct_erroris family-declared and covers what a family says its__init__can raise —()for generic,(OpencodeServerError,)for opencode. A signature mismatch is not that: an out-of-tree class whose__init__does not accept a keyword core passes raisesTypeError, which no family declares, so it escapes.Two lines up, the
kind.load()arm already states why that matters:The same is true here, and one step later. The
ImportErrorarm was given a cleanerror:line naming the profile and the kind; the construct call has no equivalent for the mismatch case.Why it is not urgent
The out-of-tree adapter-class capability (#226, PR #239) has not shipped in a tagged release — the last tag is
v0.9.1and the entry is under## [Unreleased]. The authoring guide states there is no out-of-tree adapter-class package to copy yet, so today the only classes reaching this line are the bundled ones, which are edited in the same commit as any new keyword.PR #568 documents the obligation on the author side (accept
**kwargs; the bootstrap keyword set grows), which is the cheap half and the one that belongs before first release. This issue is the core-side half: even a correct contract gets violated, and the failure should read like the rest of the bootstrap rather than like a crash.Shape
Catch
TypeErroraround the construct call and re-raise asSystemExitnaming the profile, the kind, and the offending keyword — matching theImportErrorarm above it.TypeErroronly, on the same rule that arm states: a declared failure gets a clean line, anything else is a bug in that package and must surface as itself.Care needed on one point: a
TypeErrorraised from inside a working__init__is a bug in that package and must not be relabelled as a signature mismatch. Distinguishing the two means inspecting the exception rather than merely its type — worth pinning with a test that aTypeErrorthrown from the constructor body still surfaces as itself.Related: #226, PR #239, PR #568.