Breaking a self-referential devcontainer script loop

Invoking pnpm inside a container startup script re-triggered the pnpm binary wrapper itself. Guarding execution with environment flags broke the infinite loop.

When automated developer CLI synchronization was added in developer-cli-auto-updates, a devcontainer startup hook was introduced to verify that the container’s active Corepack package manager matched the packageManager specification in package.json. However, executing pnpm inside sync-pnpm-version.sh invoked a shimmed CLI wrapper that executed sync-pnpm-version.sh before running commands. This created an infinite recursive invocation loop during container initialization, stalling devcontainer creation before shell access opened.

Identifying the recursive invocation path#

Tracing the container startup hang revealed that Corepack’s shimmed pnpm executable intercepted command calls and evaluated local synchronization scripts before passing arguments to Node.js.

#!/usr/bin/env bash
# .devcontainer/sync-pnpm-version.sh
if [ "${SYNCING_PNPM:-0}" = "1" ]; then
  exit 0
fi
export SYNCING_PNPM=1

Because the sync script called pnpm --version to check installed binaries, the binary shim re-executed sync-pnpm-version.sh, triggering an endless chain of nested shell processes.

Direct JSON inspection over binary execution#

To eliminate reliance on binary invocation during version checks, sync-pnpm-version.sh was updated to parse package.json directly using jq rather than invoking pnpm CLI commands.

Reading configuration files directly prevents CLI wrapper shims from triggering nested lifecycle scripts during container boot.

Devcontainer initialization

Extracting version strings directly from file metadata allows the script to determine whether Corepack requires an update without launching child processes.

Validating container boot stability#

With environment recursion guards and direct JSON parsing in place, container startup executes deterministically without launching extra subprocesses.

// .devcontainer/developer-clis.json
{
  "pnpm": {
    "packageManagerField": "packageManager",
    "syncScript": ".devcontainer/sync-pnpm-version.sh"
  }
}

Updating .devcontainer/developer-clis.json and verifying script execution in a fresh container environment confirmed that package manager synchronization completes cleanly without delaying devcontainer startup.