Skip to content

fix(builder): skip runtime resolution for a forced-native build - #24

Closed
duplo-darren wants to merge 1 commit into
duplocloud:devfrom
duplo-darren:fix/builder-skip-resolve-when-native
Closed

duplo-darren wants to merge 1 commit into
duplocloud:devfrom
duplo-darren:fix/builder-skip-resolve-when-native

Conversation

@duplo-darren

Copy link
Copy Markdown

Bug

RUNTIME=podman in .env breaks every containerized extension build, regardless of extension.

Repro:

  1. Set RUNTIME=podman in .env (the macOS example in docs/configuration.md and docs/getting-started/prerequisites.md).
  2. Run ./scripts/build-extension.sh extensions/<any>.

Observed:

ERROR: RUNTIME='podman' was requested but 'podman' is not on PATH.

This fires after the builder container is created — podman successfully launches it, then the build fails inside.

Root cause

build-extension.sh re-invokes itself inside the builder container to do the actual dotnet/npm work. docker-compose.yml's builder service sets DUPLO_BUILD_NATIVE=1 in its environment for exactly this re-entrant call — nesting a container runtime inside the build container isn't supported, and builder_mode() already has a documented branch for it.

But builder_dispatch() called runtime_resolve unconditionally, before ever checking DUPLO_BUILD_NATIVE. The whole repo — .env included — is bind-mounted into the container at /work, so the inner invocation's _envv lookup reads RUNTIME=podman straight off the mounted file, runtime_requested() returns non-empty, and the existing fallback treats that as an explicit pin and fatals — before builder_mode()'s want_native=1 short-circuit ever gets evaluated.

RUNTIME=docker, the default, usually masks this: docker is typically also on the host's PATH, so the same resolve call that should be skipped happens to succeed by accident. Same latent bug either way.

Fix

Guard the runtime_resolve call with the already-computed want_native flag, so a forced-native build — which the container always is — skips runtime resolution entirely instead of racing it:

-  runtime_resolve 2>/dev/null || {
-    if [ -n "$(runtime_requested)" ]; then runtime_resolve; exit 1; fi
-  }
+  if [ "$want_native" != 1 ]; then
+    runtime_resolve 2>/dev/null || {
+      if [ -n "$(runtime_requested)" ]; then runtime_resolve; exit 1; fi
+    }
+  fi

No behavior change on the host-side (outer) dispatch, where want_native is unset by default and the existing resolve-or-die logic still runs.

Verification

Reproduced the exact reported error in isolation against the unpatched code (same message, same exit 1), confirmed the fix resolves it, and confirmed the host-side path — a developer who names a runtime they genuinely don't have — still fatals correctly rather than silently going native.

Also verified end-to-end: rebuilding extensions/soc2-posture-extension with RUNTIME=podman set succeeds after the fix, fails identically before it.

Testing

New file — none existed for _builder.sh before. 5 tests, confirmed RED against the unpatched code (exactly the forced-native/unresolvable-RUNTIME case fails, nothing else), then GREEN with the fix. Full suite: 137 assertions across 8 files, all passing.

🤖 Generated with Claude Code

RUNTIME=podman in .env breaks every containerized extension build, regardless of extension — reported
with a full root-cause trace and a live rebuild confirming both the failure and the fix.

build-extension.sh re-invokes itself inside the builder container to do the actual dotnet/npm work.
docker-compose.yml's builder service sets DUPLO_BUILD_NATIVE=1 in its environment for exactly this
re-entrant call — nesting a container runtime inside the build container isn't supported, and
builder_mode() already has a documented branch for it. But builder_dispatch() called runtime_resolve
unconditionally, before ever checking DUPLO_BUILD_NATIVE. Since the whole repo (.env included) is
bind-mounted into the container at /work, the inner invocation's _envv lookup read RUNTIME=podman
straight off the mounted file, runtime_requested() returned non-empty, and the fallback treated that as
an explicit pin and fataled — before builder_mode()'s want_native=1 short-circuit ever got evaluated.

RUNTIME=docker, the default, usually masks this: docker is typically also on the host's PATH, so the
same resolve call that should be skipped happens to succeed by accident. Same latent bug either way.

Fix: guard the runtime_resolve call with the already-computed want_native flag, so a forced-native
build — which the container always is — skips runtime resolution entirely instead of racing it. No
behavior change on the host-side (outer) dispatch, where want_native is unset by default and the
existing resolve-or-die logic still runs.

Verified both ways: reproduced the exact reported error in isolation against the unpatched code (same
message, same exit 1), confirmed the fix resolves it, and confirmed the host-side path — a developer
who names a runtime they genuinely don't have — still fatals correctly rather than silently going
native. Also verified end-to-end: rebuilding extensions/soc2-posture-extension with RUNTIME=podman set
succeeds after the fix, fails identically before it.

Tests: 5, new file (none existed for _builder.sh before). Confirmed RED against the unpatched code —
exactly the forced-native/unresolvable-RUNTIME case fails, nothing else — then GREEN with the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@duplo-darren

Copy link
Copy Markdown
Author

Combined into #21 (same content, cherry-picked as 7b18fe7) rather than kept as a separate PR — no file overlap with #21 and neither had review activity yet, so there was no cost to folding it in. Closing as a duplicate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant