Skip to content

perf(npm): copy-over-shim for near-native CLI startup#69

Merged
amondnet merged 3 commits into
mainfrom
amondnet/copy-over-shim
Jul 1, 2026
Merged

perf(npm): copy-over-shim for near-native CLI startup#69
amondnet merged 3 commits into
mainfrom
amondnet/copy-over-shim

Conversation

@amondnet

@amondnet amondnet commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What & why

Replaces the npm wrapper's spawn shim with the esbuild/ast-grep-style copy-over-shim, so the installed .bin/csp resolves directly to the native Rust binary — no Node.js process on the hot path. This preserves the standalone-binary / no-runtime distribution model (does not convert to napi); the Node launcher stays only as a fallback.

Startup measurement

csp --version through a real installed .bin/csp (macOS, quiet machine, hyperfine):

Launch path Median startup
Spawn shim (current) — Node boot + spawnSync 58.9 ms ± 1.2
Copy-over (this PR) — native binary directly 5.3 ms ± 0.5

≈11× faster, eliminating ~54 ms of Node boot + spawn per invocation.

Where the win applies (and where it doesn't)

The copy-over runs as a postinstall. Verified end-to-end with npm pack + install:

  • npm / pnpm install, and bun install with @pleaseai/csp in trustedDependencies → copy-over active, fast path.
  • ⚠️ Default bun install blocks lifecycle scripts for untrusted deps (Blocked 1 postinstall) → stays on the JS launcher. Still fully functional, no regression, just no speedup.
  • ⚠️ bunx @pleaseai/csp (README's headline entrypoint, incl. MCP config) is ephemeral + untrusted → always the launcher.

So for the default bunx path this is correct and non-regressing but only speeds up if users opt in via trustedDependencies (documented) or use an installed project bin.

Changes

  • npm/csp/install.js (new postinstall): copies (hard-links, falls back to byte-copy) the platform binary over bin/csp.js via temp + atomic rename. Best-effort (always exits 0) and idempotent via an inode short-circuit (avoids the POSIX same-inode rename() no-op that would leave a stray temp file on npm rebuild).
  • npm/csp/lib/resolve.js (new): shared platform resolution required by both the launcher and the postinstall — never overwritten, so re-running postinstall never require()s a native binary as CommonJS.
  • npm/csp/bin/csp.js (hardened fallback launcher): forwards argv, stdio, exit code, and SIGINT/SIGTERM/SIGHUP so killing the launcher cleanly stops a long-running csp mcp child (no orphan); TTY-only fast-path hint (CSP_NO_FALLBACK_WARNING=1 to mute); resolves a dev target/ binary from a source checkout. The dev resolver is intentionally not used by the postinstall, so it can never clobber the source shim.
  • generate-platform-packages.mjs: ships install.js + lib/resolve.js in the published wrapper.
  • package.json: files + postinstall.
  • Docs: npm/README.md + CLAUDE.md (model, numbers, bun caveat).

Verification

  • Copy-over activates on real npm install; idempotent over repeated runs (no stray temp).
  • Fallback: functional, exit-code propagation, TTY-gated warn (silent when piped/MCP), SIGTERM → child reaped, launcher exits 143 (old spawnSync orphaned it), dev-target fallback.
  • install.js does not clobber the source bin/csp.js in a checkout with target/ built.
  • eslint + tsc --noEmit clean.

Summary by cubic

Switch the npm wrapper to a copy-over shim so project .bin/csp runs the native Rust csp directly. This removes Node from startup and cuts launch time from ~59 ms to ~5–12 ms (~10–11×), with the Node launcher kept as a fallback when postinstall is skipped (e.g. --ignore-scripts or bun without trust).

  • New Features

    • Postinstall npm/csp/install.js copies or hard-links the platform binary over bin/csp.js via atomic rename; best-effort, idempotent, and guarded to run only under node_modules/.
    • Shared resolver npm/csp/lib/resolve.js used by installer and launcher; avoids requiring a native binary and hardens libc detection by temporarily setting process.report.excludeNetwork = true.
    • Hardened fallback npm/csp/bin/csp.js: forwards argv/stdio/exit and SIGINT/SIGTERM/SIGHUP, prints a TTY-only hint (mentions --ignore-scripts and bun trust), and supports a dev target/ binary; Windows intentionally stays on the JS launcher.
    • Packaging/docs: npm/csp/package.json adds postinstall and ships install.js/lib/resolve.js; generator updated to include both.
  • Bug Fixes

    • Linux musl/arm64: resolver now returns null instead of selecting the glibc arm64 build; generator adds libc: "glibc" to @pleaseai/csp-linux-arm64 so musl arm64 is skipped cleanly.
    • Idempotency: install short-circuit now compares device + inode to handle cross-device cases without leaving temp files.

Written for commit 71ed3af. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added an install-time native setup for the npm wrapper so it can directly execute the correct platform binary when available.
    • Introduced a runtime-resolved fallback launcher with improved signal forwarding and interactive TTY warning behavior.
  • Bug Fixes

    • Made the copy-over step safer and more reliable with idempotent checks and atomic, best-effort replacement (leaving the fallback intact on failure).
  • Documentation

    • Expanded packaging docs to explain the direct-run vs fallback flow end-to-end, including startup-cost measurements and a bun lifecycle-scripts note.

Replace the spawn-shim launcher with the esbuild/ast-grep copy-over model so
`.bin/csp` resolves directly to the native Rust binary — no Node.js process on
the hot path. Measured `csp --version` via the installed bin drops from ~59 ms
(Node boot + spawnSync) to ~5 ms (~11x faster); the win applies to npm/pnpm
installs and bun installs that trust the package.

- add `install.js` postinstall: copies (hard-links, falls back to byte-copy)
  the resolved platform binary over `bin/csp.js` via temp + atomic rename.
  Best-effort (always exits 0) and idempotent via an inode short-circuit that
  avoids the POSIX same-inode rename() no-op leaving a stray temp file.
- extract shared platform resolution into `lib/resolve.js` (never overwritten),
  required by both the launcher and the postinstall so `npm rebuild` never
  require()s a native binary as CommonJS.
- harden `bin/csp.js` as the fallback launcher (used when postinstall is
  skipped, e.g. bun-untrusted / `bunx` / --ignore-scripts): forward argv,
  stdio, exit code, and SIGINT/SIGTERM/SIGHUP so killing the launcher cleanly
  stops a long-running `csp mcp` child instead of orphaning it; print a
  TTY-only fast-path hint; resolve a dev `target/` binary from a source
  checkout. The dev resolver is NOT used by the postinstall, so it can never
  clobber the source shim.
- generator ships install.js + lib/resolve.js in the wrapper; package.json
  files/postinstall updated.
- document the model, startup numbers, and the bun `trustedDependencies`
  caveat in npm/README.md and CLAUDE.md.
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 48dd4653-af41-403e-b9e2-cdf221a78bfb

📥 Commits

Reviewing files that changed from the base of the PR and between c43be18 and 71ed3af.

📒 Files selected for processing (3)
  • npm/csp/install.js
  • npm/csp/lib/resolve.js
  • npm/scripts/generate-platform-packages.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
  • npm/csp/install.js
  • npm/csp/lib/resolve.js

📝 Walkthrough

Walkthrough

This PR adds a shared platform resolver, a postinstall copy-over that can replace the JS launcher with the native binary, a fallback launcher path with signal forwarding, and updates package wiring plus documentation.

Changes

Postinstall copy-over launcher

Layer / File(s) Summary
Shared platform resolver module
npm/csp/lib/resolve.js
Adds resolvePlatformPackage, isMusl, resolveBinaryPath, and resolveDevBinaryPath to map platform/arch to package/binary and resolve installed or locally built binary paths.
Postinstall copy-over script
npm/csp/install.js
Adds a postinstall script that resolves the native binary, checks idempotency via inode comparison, replaces bin/csp.js via hard-link or copy+chmod, atomically renames it in, and cleans up temp files.
Runtime fallback launcher
npm/csp/bin/csp.js
Reworks the launcher to use shared resolvers, emit a TTY fallback warning, spawn asynchronously with inherited stdio, handle spawn errors, and forward SIGINT/SIGTERM/SIGHUP with exit-code propagation.
Package wiring and generator updates
npm/csp/package.json, npm/scripts/generate-platform-packages.mjs
Adds postinstall script and files whitelist entries; updates the wrapper generator to create a lib directory and copy install.js and lib/resolve.js into generated packages.
Documentation updates
CLAUDE.md, npm/README.md
Updates packaging decision wording and expands the README with fallback/copy-over mechanics, startup cost benchmarks, bun package-manager notes, and an updated Layout section.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant npm
  participant installjs as install.js
  participant resolvejs as resolve.js
  participant shim as bin/csp.js

  npm->>installjs: run postinstall
  installjs->>resolvejs: resolveBinaryPath()
  resolvejs-->>installjs: native binary path or null
  installjs->>shim: compare inode with binary
  alt shim not yet replaced
    installjs->>shim: hard-link or copy binary to temp file
    installjs->>shim: rename temp file over bin/csp.js
  else already copied
    installjs-->>npm: no-op
  end
  installjs-->>npm: exit 0
Loading
sequenceDiagram
  participant User
  participant Launcher as bin/csp.js
  participant Resolver as resolve.js
  participant NativeBinary

  User->>Launcher: invoke csp
  Launcher->>Resolver: resolveBinaryPath() / resolveDevBinaryPath()
  Resolver-->>Launcher: binary path or null
  alt binary found
    Launcher->>User: emit TTY fallback warning (unless suppressed)
    Launcher->>NativeBinary: spawn(argv, inherited stdio)
    User--)Launcher: SIGINT/SIGTERM/SIGHUP
    Launcher--)NativeBinary: forward signal
    NativeBinary-->>Launcher: exit code / signal
    Launcher-->>User: propagate exit code or re-raise signal
  else binary not found
    Launcher-->>User: error - platform package not installed
  end
Loading

Possibly related PRs

  • pleaseai/code-search#55: Both PRs modify the wrapper copy-over generation in npm/scripts/generate-platform-packages.mjs to include additional published wrapper files.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: switching the npm CLI to a copy-over shim for faster startup.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch amondnet/copy-over-shim

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Comment @coderabbitai help to get the list of available commands.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an esbuild-style copy-over-shim optimization to the @pleaseai/csp npm wrapper. During postinstall, the native platform binary is copied or hard-linked directly over the JS launcher shim (bin/csp.js), eliminating Node.js startup overhead on subsequent executions. A fallback runtime launcher is maintained for environments where postinstall scripts are blocked (e.g., under Bun by default). Feedback on the changes highlights a performance risk in the libc detection logic (isMusl), where calling process.report.getReport() may cause synchronous network lookups that block the main thread, and suggests disabling network lookups during this call.

Comment thread npm/csp/lib/resolve.js
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 5 untouched benchmarks


Comparing amondnet/copy-over-shim (71ed3af) with main (2bf9c70)

Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the Node.js spawn shim in @pleaseai/csp with an esbuild-style copy-over-shim, achieving ~11× faster startup by having postinstall hard-link (or byte-copy) the native platform binary over bin/csp.js so .bin/csp resolves directly to native code with no Node.js process on the hot path.

  • install.js (new postinstall): atomically replaces bin/csp.js via temp-file + rename, with a source-checkout guard (node_modules path check), Windows skip, inode-equality idempotency short-circuit, and fully best-effort error handling (always exits 0).
  • lib/resolve.js (new): shared platform resolution for both the installer and the fallback launcher; never overwritten by the copy-over, ensuring npm rebuild stays idempotent; isMusl() now sets process.report.excludeNetwork = true to avoid a blocking reverse-DNS lookup during report generation.
  • bin/csp.js (hardened fallback): switched from spawnSync to spawn with SIGINT/SIGTERM/SIGHUP forwarding and signal re-raise on exit, dev-binary path for source checkouts, and a TTY-gated hint when the copy-over did not run.

Confidence Score: 5/5

Safe to merge — the copy-over is fully best-effort and idempotent, the fallback launcher preserves all existing behavior, and the source-checkout guard prevents accidental mutation of tracked files.

All failure modes in install.js leave the original JS launcher in place and exit 0. The atomic rename, inode idempotency check, and node_modules path guard are correctly implemented. The only notable trade-off is that the fallback launcher's signal forwarding delivers SIGINT twice to the child in an interactive terminal session, but this has no impact on the primary MCP use case and no impact on the copy-over fast path at all.

No files require special attention; all changes are additive and the existing fallback path is preserved unchanged.

Important Files Changed

Filename Overview
npm/csp/bin/csp.js Hardened fallback launcher: switched from spawnSync to spawn, added SIGINT/SIGTERM/SIGHUP forwarding, exit-code re-raise, and dev-binary fallback; double-SIGINT possible in interactive process groups
npm/csp/install.js New postinstall copy-over: atomic link+rename, inode-equality idempotency guard, source-checkout guard, Windows skip, and best-effort cleanup in finally — all edge cases well handled
npm/csp/lib/resolve.js New shared resolver: platform detection, improved isMusl() with excludeNetwork guard, resolveBinaryPath, and resolveDevBinaryPath; isMusl correctly kept private
npm/csp/package.json Added postinstall script, install.js and lib/resolve.js to files array — consistent with the new copy-over approach
npm/scripts/generate-platform-packages.mjs Copies install.js and lib/resolve.js into the generated wrapper; adds libc: 'glibc' to the arm64 Linux target — correctly pairing with resolve.js returning null for musl arm64
npm/README.md Expanded docs: startup cost table, copy-over description, bun trustedDependencies caveat, and updated layout section
CLAUDE.md Updated architecture description to reflect copy-over-shim design and new file layout

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[npm/pnpm/bun install] --> B{postinstall blocked?}
    B -->|No| C[install.js runs]
    B -->|Yes - bun untrusted or ignore-scripts| F

    C --> D{Source checkout? no node_modules in path}
    D -->|Yes| F[bin/csp.js stays as JS launcher]
    D -->|No| E{Windows?}
    E -->|Yes| F
    E -->|No| G[resolveBinaryPath]
    G --> H{Platform pkg installed?}
    H -->|No| F
    H -->|Yes| I{Same inode? idempotency}
    I -->|Yes| J[Already replaced - skip]
    I -->|No| K[linkSync or copyFileSync to temp]
    K --> L[renameSync temp over bin/csp.js]
    L --> M[.bin/csp resolves to native binary]

    F --> N[User runs .bin/csp]
    M --> O[User runs .bin/csp]
    O --> P[Native Rust - 5-12ms startup]
    N --> Q[bin/csp.js JS launcher - 60ms startup]
    Q --> R[resolveBinaryPath or resolveDevBinaryPath]
    R --> S[spawn native binary with signal forwarding]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[npm/pnpm/bun install] --> B{postinstall blocked?}
    B -->|No| C[install.js runs]
    B -->|Yes - bun untrusted or ignore-scripts| F

    C --> D{Source checkout? no node_modules in path}
    D -->|Yes| F[bin/csp.js stays as JS launcher]
    D -->|No| E{Windows?}
    E -->|Yes| F
    E -->|No| G[resolveBinaryPath]
    G --> H{Platform pkg installed?}
    H -->|No| F
    H -->|Yes| I{Same inode? idempotency}
    I -->|Yes| J[Already replaced - skip]
    I -->|No| K[linkSync or copyFileSync to temp]
    K --> L[renameSync temp over bin/csp.js]
    L --> M[.bin/csp resolves to native binary]

    F --> N[User runs .bin/csp]
    M --> O[User runs .bin/csp]
    O --> P[Native Rust - 5-12ms startup]
    N --> Q[bin/csp.js JS launcher - 60ms startup]
    Q --> R[resolveBinaryPath or resolveDevBinaryPath]
    R --> S[spawn native binary with signal forwarding]
Loading

Reviews (3): Last reviewed commit: "fix(npm): musl-arm64 resolution + cross-..." | Re-trigger Greptile

Comment thread npm/csp/bin/csp.js
Comment thread npm/csp/lib/resolve.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@npm/csp/install.js`:
- Around line 35-77: The install flow in `install.js` currently rewrites the
tracked launcher at `shimPath` in place, which can mutate the repo checkout and
poison later packaging. Update the `postinstall` path to only replace
`bin/csp.js` when running from an installed package, or switch the
`linkSync`/`copyFileSync` + `renameSync` logic to write from an immutable
template instead of the tracked file. Keep the existing idempotency and cleanup
behavior around `tempPath`, but ensure
`npm/scripts/generate-platform-packages.mjs` never reads a locally rewritten
launcher.

In `@npm/README.md`:
- Around line 81-84: The README entry for csp/install.js currently implies
bin/csp.js is always overwritten, but install.js only performs the copy in
supported, successful postinstall cases. Update the wording in the csp package
section to make the overwrite note conditional, and reference the install.js
postinstall copy-over behavior plus the bin/csp.js launcher so it clearly states
the file is left untouched on Windows, unsupported platforms, skipped lifecycle
scripts, or filesystem failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9fd071f5-bd23-48bb-a5b5-01d7ebdc8b47

📥 Commits

Reviewing files that changed from the base of the PR and between 2bf9c70 and a2fcf0c.

📒 Files selected for processing (7)
  • CLAUDE.md
  • npm/README.md
  • npm/csp/bin/csp.js
  • npm/csp/install.js
  • npm/csp/lib/resolve.js
  • npm/csp/package.json
  • npm/scripts/generate-platform-packages.mjs

Comment thread npm/csp/install.js
Comment thread npm/README.md

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 7 files

Architecture diagram
sequenceDiagram
    participant PM as Package Manager (npm/pnpm/bun)
    participant Install as install.js (postinstall)
    participant Resolver as resolve.js (shared resolver)
    participant FS as Filesystem
    participant Bin as .bin/csp (symlink)
    participant Native as Native binary (csp)
    participant User

    Note over PM,User: Installation flow (postinstall)

    PM->>Install: npm/pnpm install / bun install (if trusted)
    Install->>Resolver: resolveBinaryPath()
    Resolver->>Resolver: Map platform/arch/libc
    alt Unsupported platform (e.g. win32) or optional dep missing
        Resolver-->>Install: return null
        Install-->>PM: exit 0 (no op)
    else Binary found
        Resolver-->>Install: path to platform binary
        Install->>FS: stat(bin/csp.js) && stat(binary)
        alt Same inode (already hardlinked)
            Install->>Install: skip (idempotent)
        else Different inode
            Install->>FS: linkSync(binary, temp) or copyFileSync + chmod
            Install->>FS: renameSync(temp, bin/csp.js)
            Note over Install: atomic overwrite, temp cleaned up
            FS-->>Install: success
            Install-->>PM: exit 0
        end
    end

    Note over PM,Bin: .bin/csp now points to native binary<br/>No Node.js involved on hot path

    User->>Bin: csp --version

    alt Postinstall succeeded (symlink → native binary)
        Bin->>Native: Execute directly (no Node)
        Native-->>User: version output (fast ~5ms)
    else Postinstall did not run (e.g. bun default, win32)
        Note over Bin: .bin/csp → node bin/csp.js (fallback launcher)
        Bin->>Bin: require('./lib/resolve.js')
        Bin->>Resolver: resolveBinaryPath()
        alt Binary found
            Resolver-->>Bin: path to platform binary
        else Binary not found (optional dep missing)
            Bin->>Resolver: resolveDevBinaryPath()
            alt Dev checkout found
                Resolver-->>Bin: target/release/csp
            else Not found
                Bin->>Bin: exit with error message
            end
        end
        Bin->>Native: spawnSync(csp, argv)
        Native-->>Bin: stdout/stderr/exit code
        Bin->>Bin: Forward signals (SIGINT/SIGTERM/SIGHUP)
        opt TTY and not CSP_NO_FALLBACK_WARNING
            Bin->>User: print hint (fast path available)
        end
        Bin-->>User: exit code matching child
        Note over Bin,Native: ~60ms including Node boot
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread npm/csp/lib/resolve.js Outdated
Comment thread npm/csp/install.js Outdated
Comment thread npm/csp/lib/resolve.js
Comment thread npm/csp/bin/csp.js Outdated
Review feedback from CodeRabbit / Greptile / Gemini on the copy-over-shim:

- install.js: only run the copy-over when installed under node_modules. From a
  source checkout `bin/csp.js` is tracked and the generator reads it into every
  wrapper, so an in-place rewrite could ship a native binary as the launcher.
  Verified the guard blocks the clobber even when the platform binary resolves.
- lib/resolve.js: set `process.report.excludeNetwork = true` around
  getReport() (restored after) so libc detection can't trigger a blocking
  reverse-DNS lookup; stop exporting the internal `isMusl` helper.
- bin/csp.js: broaden the fallback hint to mention `--ignore-scripts`, not just
  bun trustedDependencies.
- README: make the bin/csp.js overwrite note conditional (Windows / unsupported
  / skipped scripts / copy failure leave it as the fallback launcher).
@amondnet

amondnet commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in c43be18:

  • [CodeRabbit / Gemini — Major] in-place rewrite of the tracked launcher can poison packaginginstall.js now only runs the copy-over when installed under a node_modules/ path segment. In a source checkout bin/csp.js is left untouched, so the generator can never read a locally-rewritten binary into published wrappers. Verified with an adversarial test: even when the platform binary is resolvable, running install.js outside node_modules leaves bin/csp.js as JS.
  • [Gemini — High] process.report.getReport() blocking reverse-DNSisMusl now sets process.report.excludeNetwork = true around the call (restored afterward).
  • [Greptile — P2] fallback warning only mentioned bun — broadened to also cover --ignore-scripts.
  • [Greptile — P2] isMusl exported unnecessarily — no longer exported (internal helper).
  • [CodeRabbit] README overwrite note read as unconditional — clarified it's conditional (Windows / unsupported platform / skipped scripts / copy failure keep the JS launcher).

eslint + tsc --noEmit clean; copy-over still activates on a real npm install.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
npm/csp/lib/resolve.js (1)

53-79: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Gate this workaround on Node 22.12.0+

process.report.excludeNetwork didn’t fully suppress the blocking libuv network queries until Node 22.12.0, so on Node 22.0.0–22.11.x this can still hang during getReport().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@npm/csp/lib/resolve.js` around lines 53 - 79, Gate the workaround in isMusl()
on Node 22.12.0+ because process.report.excludeNetwork is not reliable on
22.0.0–22.11.x and can still hang during process.report.getReport(). Update the
logic in resolve.js so the excludeNetwork toggle and report-based musl detection
only run on supported Node versions, and fall back to returning false for older
22.x releases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@npm/csp/lib/resolve.js`:
- Around line 53-79: Gate the workaround in isMusl() on Node 22.12.0+ because
process.report.excludeNetwork is not reliable on 22.0.0–22.11.x and can still
hang during process.report.getReport(). Update the logic in resolve.js so the
excludeNetwork toggle and report-based musl detection only run on supported Node
versions, and fall back to returning false for older 22.x releases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 797b3ae6-8029-4d54-aacc-57c4be92b8f7

📥 Commits

Reviewing files that changed from the base of the PR and between a2fcf0c and c43be18.

📒 Files selected for processing (4)
  • npm/README.md
  • npm/csp/bin/csp.js
  • npm/csp/install.js
  • npm/csp/lib/resolve.js
✅ Files skipped from review due to trivial changes (1)
  • npm/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • npm/csp/bin/csp.js
  • npm/csp/install.js

Second round of PR review (cubic):

- resolve.js: on musl arm64 (Alpine/aarch64) return null instead of the glibc
  arm64 package, so the launcher reports an unsupported platform rather than
  exec'ing an incompatible glibc binary. Add a matching `libc: 'glibc'`
  constraint to the linux-arm64 target so npm/bun skip it on musl arm64.
- install.js: compare device + inode (not inode alone) in the idempotency
  short-circuit — inode numbers are only unique within a filesystem.
@amondnet

amondnet commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Second review round addressed in 71ed3af:

  • [cubic P2] musl arm64 → glibc binaryresolvePlatformPackage now returns null for musl arm64 (no musl-arm64 build exists) so the launcher reports unsupported instead of exec'ing an incompatible glibc binary; added a matching libc: 'glibc' constraint to the linux-arm64 target so it isn't installed on Alpine/aarch64.
  • [cubic P2] idempotency inode collision — the copy-over short-circuit now compares dev + ino, not ino alone.

Note: several bot comments re-anchored to c43be18 (gemini's getReport HIGH, greptile's bun-only warning P2, coderabbit's "rewrite tracked launcher" Major + README note) were already fixed in c43be18 and re-posted on re-run — verified against the committed file contents:

  • getReport is wrapped in process.report.excludeNetwork = true (restored after);
  • the fallback warning already mentions --ignore-scripts;
  • install.js only runs the copy-over under a node_modules/ path (proven with an adversarial test: the source shim is left untouched even when the platform binary resolves);
  • the README overwrite note is already qualified.

eslint clean; idempotency + copy-over re-verified on a real installed layout.

@sonarqubecloud

sonarqubecloud Bot commented Jul 1, 2026

Copy link
Copy Markdown

@amondnet
amondnet merged commit 03d7e29 into main Jul 1, 2026
13 checks passed
@amondnet
amondnet deleted the amondnet/copy-over-shim branch July 1, 2026 15:48
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