fix: resolve symlinks when detecting direct execution entrypoint - #30
fix: resolve symlinks when detecting direct execution entrypoint#30autojack-bot[bot] wants to merge 1 commit into
Conversation
npx/npm bin invocation runs dist/index.js through a symlink
(node_modules/.bin/mcp-pirsch -> dist/index.js). Node resolves that
symlink when computing import.meta.url for an ES module, but
process.argv[1] keeps the symlink path, so the previous
`import.meta.url === new URL(process.argv[1], 'file://').href` check
never matched. main() was silently never invoked and the process
exited with code 0 without printing anything, which is exactly what
AutoHub observed on startup ("process exited unexpectedly, code 0").
Resolve both sides through realpathSync/fileURLToPath before
comparing so symlinked bin invocations (the normal npx/npm path) are
correctly recognized as direct execution. Adds a regression test that
spawns the entrypoint through a symlinked bin path and asserts the
server actually starts.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b7fc14f4cf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // comparison never matches and the server silently exits without | ||
| // ever calling main(). Resolve both sides to their real path first. | ||
| try { | ||
| return fileURLToPath(import.meta.url) === realpathSync(process.argv[1]); |
There was a problem hiding this comment.
Resolve the import path before comparing
When users run the bin symlink with NODE_OPTIONS=--preserve-symlinks-main or pass that Node flag, Node preserves the symlink for the main module (node --help: “preserve symbolic links when resolving the main module”), so fileURLToPath(import.meta.url) remains the symlink while realpathSync(process.argv[1]) is the target. In that environment this returns false and the server again exits without calling main(), so the comparison should realpath the import.meta.url side too.
Useful? React with 👍 / 👎.
Summary
npx/npm bin invocation runs this server through a symlink(
node_modules/.bin/mcp-pirsch -> dist/index.js). Node resolves that symlinkwhen computing
import.meta.urlfor an ES module, butprocess.argv[1]keeps the un-resolved symlink path. The old check —
— compares a resolved path against an unresolved one, so it never matches
when the server is launched the normal way (via
npx @verygoodplugins/mcp-pirsch@latestor the
mcp-pirschbin).main()is silently never called, nothing isprinted, and the process exits with code 0.
This is exactly the symptom reported by an AutoHub user: the server "crashes"
on startup with
process exited unexpectedly, code 0and no error output atall — because there was no error, the entrypoint guard just never let
main()run.
Fix: resolve both sides with
fileURLToPath/realpathSyncbefore comparing.Root cause verification
Reproduced locally by symlinking a built
dist/index.jsinto anode_modules/.bin/mcp-pirsch-style path and invoking it exactly as npx would(stdin held open, required env vars set):
0, no stdout/stderr —matches the reported bug.
stderrprintsPirsch MCP server running, and the stdio transport stays open as expected.Changes
src/index.ts: replace the string-URL comparison with arealpathSync/fileURLToPath-based comparison so symlinked bin invocationsare recognized as direct execution.
src/index.entrypoint.test.ts: new regression test that spawns theentrypoint through a real symlink (mirroring the
npx/npm bin layout) andasserts the server actually starts. Fails on the pre-fix code with
Server process exited early with code 0and passes after the fix.Test plan
npm run lint— passnpm run typecheck— passnpm run build— passnpm test(53 tests, including the new regression test) — passnpm run test:coverage— passdist/index.jsinvocation exits with code 0before the fix, stays running and logs
Pirsch MCP server runningafter the fix