Skip to content

fix: resolve symlinks when detecting direct execution entrypoint - #30

Open
autojack-bot[bot] wants to merge 1 commit into
mainfrom
fix/npx-symlink-main-module-detection
Open

fix: resolve symlinks when detecting direct execution entrypoint#30
autojack-bot[bot] wants to merge 1 commit into
mainfrom
fix/npx-symlink-main-module-detection

Conversation

@autojack-bot

@autojack-bot autojack-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

Summary

npx/npm bin invocation runs this server 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 un-resolved symlink path. The old check —

const isDirectExecution =
  typeof process.argv[1] === 'string' && import.meta.url === new URL(process.argv[1], 'file://').href;

— 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@latest
or the mcp-pirsch bin). main() is silently never called, nothing is
printed, 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 0 and no error output at
all — because there was no error, the entrypoint guard just never let main()
run.

Fix: resolve both sides with fileURLToPath/realpathSync before comparing.

Root cause verification

Reproduced locally by symlinking a built dist/index.js into a
node_modules/.bin/mcp-pirsch-style path and invoking it exactly as npx would
(stdin held open, required env vars set):

  • Before fix: process exits immediately, exit code 0, no stdout/stderr —
    matches the reported bug.
  • After fix: process stays alive, stderr prints Pirsch MCP server running, and the stdio transport stays open as expected.

Changes

  • src/index.ts: replace the string-URL comparison with a
    realpathSync/fileURLToPath-based comparison so symlinked bin invocations
    are recognized as direct execution.
  • src/index.entrypoint.test.ts: new regression test that spawns the
    entrypoint through a real symlink (mirroring the npx/npm bin layout) and
    asserts the server actually starts. Fails on the pre-fix code with Server process exited early with code 0 and passes after the fix.

Test plan

  • npm run lint — pass
  • npm run typecheck — pass
  • npm run build — pass
  • npm test (53 tests, including the new regression test) — pass
  • npm run test:coverage — pass
  • Manual repro: symlinked dist/index.js invocation exits with code 0
    before the fix, stays running and logs Pirsch MCP server running
    after the fix

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.
@jack-arturo

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread src/index.ts
// 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]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge 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 👍 / 👎.

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