Skip to content

Resolve a bare Windows command through PATHEXT before the bare name - #175

Merged
M3gA-Mind merged 2 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/168-windows-pathext
Nov 11, 2026
Merged

M3gA-Mind merged 2 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/168-windows-pathext

Conversation

@M3gA-Mind

Copy link
Copy Markdown
Contributor

Summary

On Windows, a stdio server with command npx failed with os error 193, %1 is not a valid Win32 application. Node installs an extensionless npx shell script beside npx.cmd. executable_candidates tried the bare name first, and any file counts as executable off Unix, so resolution picked the script and CreateProcessW refused it. That's the most common stdio MCP config.

Every PATHEXT variant is now tried before the bare name. npx resolves to npx.cmd, and a real extensionless executable with nothing beside it still resolves, which is what the existing tests expect.

Related issue

Part of #168 (PATHEXT resolution; the launch-error classification follows in its own PR) (Sentry TAURI-RUST-12J8, 12BC, 1605).

API or behavior changes

On Windows only, resolution order changes: <name><ext> for each PATHEXT entry, then <name>. Nothing changes elsewhere.

Not in this PR: reporting a spawn failure as a launch error instead of malformed mcp response. That needs a new bus error name and so a contract bump, which #164 already takes. It'll come as a follow-up.

Validation

On our Linux CI runner, at 22c0eb8:

  • cargo fmt --all -- --check: clean.
  • cargo clippy --all-targets --all-features -- -D warnings: 0 errors, same as main.
  • cargo build --all-targets --all-features: ok.
  • cargo test -p tinymcp spawn_env: 42 passed, including pathext_variants_come_before_the_bare_name.
  • cargo test --workspace: no failures unique to this branch. The 5 failures are main's known full-suite timeouts in registry::boot and tinybus_module::directories, none of them near spawn_env.
  • Red control: putting the bare name first again fails pathext_variants_come_before_the_bare_name (bare bin/npx listed before bin/npx.COM).

The on-disk Windows test runs on this PR's windows-latest job.

Tests

  • pathext_variants_come_before_the_bare_name checks the ordering on every platform. With main's order it fails.
  • a_bare_name_prefers_its_pathext_variant_over_an_extensionless_script (Windows) puts both npx and npx.cmd on disk and must resolve to the .cmd.

Documentation

The rustdoc on pathext_candidates explains the order.

Checklist

  • The change is focused on one logical change
  • No new #[allow(...)], #[ignore], or relaxed lints
  • No secrets, tokens, or .env contents in the diff or the description

executable_candidates tried the bare name first on Windows, and any file
counts as executable off Unix. Node installs an extensionless npx shell
script beside npx.cmd, so a stdio server with command npx resolved to the
script, and CreateProcessW refused it with os error 193 (%1 is not a valid
Win32 application). That is the most common stdio MCP config.

Try every PATHEXT variant first and the bare name last, so npx resolves to
npx.cmd while a real extensionless executable with nothing beside it still
resolves. pathext_candidates holds the ordering so it is tested on every
platform; a Windows-only test checks the npx/npx.cmd case on disk.

Closes tinyhumansai#168
@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8ae41fb0-fb32-45f2-b396-9bef5e1a8f1d
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@tinysweeper

tinysweeper Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Reviewed head: 31b268264f9e65c19665d6500982891bf8aa108b

Verdict: Ready for maintainer review · Priority: none
Coverage: Reviewed 2 of 2 files.
Pull request purpose (title): Resolve a bare Windo…

Diagnostics and supporting details

Full summary

Pull request purpose (title): Resolve a bare Windows command through PATHEXT before the bare name. This PR fixes the most common Windows stdio MCP configuration failing with os error 193 (`%1 is not a valid Win32 application`): Node installs an extensionless `npx` shell script beside `npx.cmd`, Windows command resolution tried the bare name first, and because any file counts as executable off Unix the script was selected and `CreateProcessW` refused it. The pull request reorders resolution so every `PATHEXT` variant is tried before the bare name, letting `npx` resolve to `npx.cmd` while a genuine extensionless executable with nothing beside it still resolves; behaviour outside Windows is unchanged. No trusted base-to-head diff was available, so no push delta is described.

Review snapshot

Change surface Files Review signal Count
Production 1 Active findings 0
Tests 1 Noted findings 0
Documentation 0 Resolved findings 0
Configuration 0 Pending checks/questions 0

Completeness: Complete
Test assessment: Test coverage is assessed from changed tests and lane evidence; execution is not claimed without trusted check data.

What changed

On Windows, command resolution in the stdio spawn environment module now enumerates `<name><ext>` candidates for each `PATHEXT` entry first and appends the bare `<name>` last, instead of trying the bare name first. The ordering is factored into a `pathext_candidates` helper gated for Windows and tests, with rustdoc explaining why the bare name is last (Node's extensionless `npx` script is rejected by `CreateProcessW`, issue #168) and that a real extensionless executable still resolves. Empty `PATHEXT` segments are skipped and the default `.COM;.EXE;.BAT;.CMD` list is used when the variable is absent. Non-Windows resolution paths are untouched. Two tests cover the reordering: a platform-independent order assertion and a Windows-only on-disk case. The latest-push delta could not be computed from trusted evidence, so only the whole-pull-request behaviour is described.

Features

  • Modified — Windows PATHEXT-first command resolution: A bare Windows command such as `npx` now resolves to its `PATHEXT` variant (`npx.cmd`) rather than to the extensionless shell script that `CreateProcessW` rejects with os error 193, while a real extensionless executable with no variant beside it still resolves; resolution order on other platforms is unchanged. (crates/tinymcp/src/transport/stdio/spawn_env/mod.rs fn executable_candidates(base: PathBuf) -> Vec<PathBuf> {, crates/tinymcp/src/transport/stdio/spawn_env/mod.rs)
  • Internal refactor — `pathext_candidates` ordering helper: Candidate enumeration is extracted into a helper gated `#[cfg(any(windows, test))]`, matching its only non-test caller and allowing the ordering to be unit-tested on every platform; its rustdoc records that the bare name comes last and why. (crates/tinymcp/src/transport/stdio/spawn_env/mod.rs, crates/tinymcp/src/transport/stdio/spawn_env/mod_tests.rs)

Tests

  • unit — `pathext_variants_come_before_the_bare_name` pins the exact candidate list produced for a base name with a `PATHEXT` value containing an empty segment, asserting the variants in order with the bare name last.: Not executed by this review. (crates/tinymcp/src/transport/stdio/spawn_env/mod_tests.rs)
  • integration — `a_bare_name_prefers_its_pathext_variant_over_an_extensionless_script` (Windows-only) places both `npx` and `npx.cmd` on disk and asserts that `locate_command` returns the `.cmd`.: Not executed by this review. (crates/tinymcp/src/transport/stdio/spawn_env/mod_tests.rs)

Findings

No active actionable findings.

Before merge

None.

Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The reordering is consistent with the cfg gating (`#[cfg(any(windows, test))]` matches the only non-test caller) and with the existing `locate_command` tests, and no defect was found that would change behaviour on a reachable input.
  • Lane summary: The change moves the bare program name to the end of the Windows candidate list, so a `PATHEXT` variant (`npx.cmd`) is preferred over Node's extensionless POSIX shim while a lone extensionless executable still resolves, and it extracts the ordering into a `pathext_candidates` helper covered by a platform-independent unit test plus a Windows-only integration test. The logic, cfg gating (`#[cfg(any(windows, test))]` matches the only non-test caller, `#[cfg(windows)] fn executable_candidates`) and the existing `locate_command` tests all remain consistent, and I found no defect that would change behaviour on a reachable input.

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The helper is correctly gated for the unconditional test import, the bare name is still appended last so extensionless executables keep resolving, and no security-relevant or functional defect was found in the changed files.
  • Lane summary: The change reorders Windows command candidates so PATHEXT variants are tried before the extensionless bare name, with the enumeration factored into a testable helper. The helper is correctly gated (`#[cfg(any(windows, test))]`) for the unconditional test import, the bare name is still appended last so extensionless executables keep resolving, and the new unit tests match the helper's actual behaviour. No security-relevant or functional defect found in these two files.

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: Both tests pin the new ordering: one asserts the exact candidate sequence (including skipping an empty `PATHEXT` segment) on every platform, and the Windows-only test would fail against the previous code while the documented fallback to a real extensionless executable is pinned by the pure test.
  • Lane summary: The reordering in `pathext_candidates` is earned by two tests: `pathext_variants_come_before_the_bare_name` pins the exact candidate order (including skipping an empty PATHEXT segment and keeping the bare name last) on every platform, and the Windows-only `a_bare_name_prefers_its_pathext_variant_over_an_extensionless_script` would fail against the old code, since `is_executable_file` on Windows accepts any file and the extensionless `npx` was previously returned first. The documented fallback to a real extensionless executable is also pinned by the pure test, which asserts the bare name is still in the list and last. No behavioural path in this diff is left unexercised.

commits

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: No sensitive content was found in the committed files.
  • Lane summary: No sensitive content in committed files.

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The description matches the diff: it states the Windows-only reordering of `<name><ext>` candidates ahead of the bare name, names the `npx.cmd` versus extensionless script case, covers both new tests and the `pathext_candidates` rustdoc, and the non-Windows path is indeed untouched.
  • Lane summary: The description matches the diff: it states the Windows-only reordering of `<name><ext>` candidates ahead of the bare name, names the npx.cmd vs. extensionless script case, mentions both new tests and the `pathext_candidates` rustdoc, and the non-Windows path is indeed untouched. Nothing the diff does is left unmentioned, and no defect surfaced in the changed code or tests.
Evidence and run details
  • Models: ladder/vectors-oai3, deepseek-v4.1-flash
  • Spend: $0.009545
  • Tokens: 173506 input · 40387 output · 65664 cached · 224 embedding
  • Updated: 2026-11-11T12:52:02Z
Head State Pass summary
31b268264f9e ready for maintainer review 0 active finding(s), 0 resolved finding(s) (at 2026-11-11T12:52:02Z)

tinysweeper 0.1.2

@tinysweeper tinysweeper 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.

tinysweeper found nothing blocking. Approving.

Diagnostics

Total spend: $0.0095

@M3gA-Mind
M3gA-Mind merged commit cc05737 into tinyhumansai:main Nov 11, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant