fix: run node CLIs on a private runtime immune to workspace node changes - #17
Merged
Merged
Conversation
The playwright and typescript-language-server features installed npm shims whose #!/usr/bin/env node shebang resolved the interpreter from PATH at spawn time. The only node in the image lives under nvm's mutable current symlink, and workspace provisioning is allowed to rewrite that state — switch versions, uninstall the build-time version, or wipe nvm outright — which took playwright-cli down in production with "env: 'node': No such file or directory" (exit 127). Each feature now provisions a private node runtime under /usr/local/share/node-runtime/v<version> (fetched from nodejs.org, checksum-verified, root-owned read-only, shared between features pinning the same version) and fronts its CLI with a /bin/sh wrapper that execs that runtime by absolute path. Nothing the CLIs run resolves through PATH or nvm anymore: playwright respawns children via process.execPath and tsserver is forked, so both stay on the private runtime. The features no longer require the node feature at all; installs use the runtime's own npm. The buildbuddy, cloud-sql-proxy, and grok features are native binaries and were already immune. Tests: the node-switch scenarios now also delete nvm entirely — the production failure mode — and new ubuntu_no_node scenarios prove both features work on an image with no node of its own. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fjd3JvBzVEfk9iiJ2JeoJh
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fjd3JvBzVEfk9iiJ2JeoJh
| fi | ||
|
|
||
| local arch | ||
| case "$(dpkg --print-architecture)" in |
There was a problem hiding this comment.
TypeScript language server can no longer be installed on Alpine images
Users adding the language server to an Alpine-based container now get a failed image build before the server is installed. Add platform-aware runtime selection or explicitly retain the prior supported path for non-Debian systems (install_node_runtime).
Prompt to fix with AI
This is a comment left during a bug review (does this PR's changed code do the wrong thing when it runs?).
Comment:
**TypeScript language server can no longer be installed on Alpine images**
Users adding the language server to an Alpine-based container now get a failed image build before the server is installed. Add platform-aware runtime selection or explicitly retain the prior supported path for non-Debian systems (`install_node_runtime`).
Issue:
The new runtime installer unconditionally executes `dpkg --print-architecture` and only downloads Node's glibc Linux archive. Alpine does not provide `dpkg` by default and uses musl, so the script exits under `set -e` before installing the language server, whereas the previous implementation used the selected node feature's working `npm`.
How it is reached:
Build a dev container from an Alpine-based image with the `typescript-language-server` feature; its feature installer invokes `install_node_runtime` before npm installation.
Suggested fix:
Detect the distribution and architecture before selecting a runtime, then install a compatible musl Node build on Alpine or preserve a supported npm/node-feature path there. If support is intentionally Debian-only, add an explicit platform guard and update the feature requirements instead of advertising no base-image requirement.
Where:
src/typescript-language-server/install.sh (install_node_runtime)
How can I resolve this? Keep the fix scoped to the PR-touched paths and make the smallest change that makes the behavior correct, following existing patterns in this codebase.
ashwin153
enabled auto-merge (squash)
August 18, 2026 15:24
The runtime fetch detects the architecture with dpkg and downloads node's glibc build, neither of which exists on alpine. Fail with an explicit message like the playwright feature does instead of dying at dpkg. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fjd3JvBzVEfk9iiJ2JeoJh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Softlight Overview
UX Score: 3/5
Suggested fixes
To run Softlight again, click here or comment @softlight.
Why
The playwright and typescript-language-server features installed npm shims whose
#!/usr/bin/env nodeshebang resolves the interpreter from the caller'sPATHat spawn time. The only node in the sandbox image lives under nvm's mutablecurrentsymlink, and workspace provisioning runs are allowed to rewrite that state — switch versions, uninstall the build-time version, or wipe nvm outright. When that happened in production, everyplugins=[playwright]request in the pod failed with:devcontainer-features#14 made the CLI nvm-proof (own prefix +
/usr/local/binsymlink), but its interpreter still came fromPATH. This closes the other half.What
/usr/local/share/node-runtime/v<nodeVersion>: fetched from nodejs.org, checksum-verified againstSHASUMS256.txt, root-owned and read-only, keyed by version so features pinning the samenodeVersion(default 24.18.0) share one copy./usr/local/bin/playwright-cliand/usr/local/bin/typescript-language-serverare now/bin/shwrappers that exec that runtime by absolute path instead of symlinks to the npm shims. Nothing these CLIs run resolves throughPATHor nvm anymore: playwright respawns its children viaprocess.execPath, and tsserver is spawned viafork(), so the whole tree stays on the private runtime.installsAfteron node dropped, works on images with no node at all). A newnodeVersionoption pins the runtime.:1consumers pick this up on their next image build.Verification
pre-commit run --all-filespasses (shellcheck, shfmt, json/yaml hooks).install.shverbatim on a clean container: runtime download + checksum verify + npm install + wrapper all succeed;typescript-language-server --versionand a full LSP diagnostics round-trip (forked tsserver) work withPATH=/usr/bin:/bin— no node resolvable anywhere.playwright-cli install --skills claude— through the new wrapper withPATH=/usr/bin:/bin: exit 0, skill installed.ubuntu_no_nodescenarios install each feature without the node feature and assertnode/npmare absent while the CLIs work.🤖 Generated with Claude Code
https://claude.ai/code/session_01Fjd3JvBzVEfk9iiJ2JeoJh
Generated by Claude Code
Note
Cursor Bugbot is generating a summary for commit debf558. Configure here.