Summary
internal/cli/run.go's resolveBackendFromConfigData() calls config.ParsePerRepoConfig/config.ParseOrgConfig (pure YAML unmarshal, no .Validate() call) and passes the result straight into agentruntime.ResolveFromConfig/ResolveFromPerRepoConfig → Resolve(), which switches on the runtime name with no check against config.ValidRuntimes().
This means runtime: opencode or runtime: pi in org/per-repo config resolves successfully (both are registered in Resolve()'s switch for dev/testing purposes) even though neither is in ValidRuntimes() and both are non-functional stubs. A config author who sets one of these runtimes will have the run proceed through sandbox creation, fetch-service setup, and bootstrap/env wiring before failing inside rt.Bootstrap() — burning real (if bounded) side effects before the failure surfaces, rather than failing fast at config-parse time.
Scope
This affects both stub runtimes identically:
Confirmed via git history that this is not specific to either PR — the gap has existed since opencode's registration and pi simply inherited it.
Suggested fix
Add a ValidRuntimes() guard directly inside agentruntime.Resolve() (or call .Validate() in resolveBackendFromConfigData before resolving), so config-driven runtime selection — org or per-repo — fails immediately, before any sandbox is created, for any runtime not in ValidRuntimes(). Keep runtime.Resolve("pi")/Resolve("opencode") working for explicit dev/testing call sites that don't go through config.
Update the tests in internal/runtime/registry_test.go that currently assert opencode/pi resolve successfully via ResolveFromPerRepoConfig — once the guard lands, that codepath should return an error instead.
References
Originally raised by an automated review bot on #6466 (review thread), confirmed and scoped via independent review-squad analysis (Claude + Grok) which traced the actual fullsend run side-effect ordering to establish this is bounded to sandbox/bootstrap overhead (no wasted inference calls, since Bootstrap() fails before Run() is ever reached).
Summary
internal/cli/run.go'sresolveBackendFromConfigData()callsconfig.ParsePerRepoConfig/config.ParseOrgConfig(pure YAML unmarshal, no.Validate()call) and passes the result straight intoagentruntime.ResolveFromConfig/ResolveFromPerRepoConfig→Resolve(), which switches on the runtime name with no check againstconfig.ValidRuntimes().This means
runtime: opencodeorruntime: piin org/per-repo config resolves successfully (both are registered inResolve()'s switch for dev/testing purposes) even though neither is inValidRuntimes()and both are non-functional stubs. A config author who sets one of these runtimes will have the run proceed through sandbox creation, fetch-service setup, and bootstrap/env wiring before failing insidert.Bootstrap()— burning real (if bounded) side effects before the failure surfaces, rather than failing fast at config-parse time.Scope
This affects both stub runtimes identically:
opencode(added in refactor(runtime): register opencode as a stub runtime #6035)pi(added in refactor(runtime): register pi as a stub runtime #6466)Confirmed via git history that this is not specific to either PR — the gap has existed since
opencode's registration andpisimply inherited it.Suggested fix
Add a
ValidRuntimes()guard directly insideagentruntime.Resolve()(or call.Validate()inresolveBackendFromConfigDatabefore resolving), so config-driven runtime selection — org or per-repo — fails immediately, before any sandbox is created, for any runtime not inValidRuntimes(). Keepruntime.Resolve("pi")/Resolve("opencode")working for explicit dev/testing call sites that don't go through config.Update the tests in
internal/runtime/registry_test.gothat currently assertopencode/piresolve successfully viaResolveFromPerRepoConfig— once the guard lands, that codepath should return an error instead.References
Originally raised by an automated review bot on #6466 (review thread), confirmed and scoped via independent review-squad analysis (Claude + Grok) which traced the actual
fullsend runside-effect ordering to establish this is bounded to sandbox/bootstrap overhead (no wasted inference calls, sinceBootstrap()fails beforeRun()is ever reached).