Skip to content

refactor(acpx): split Windows command parsing#60692

Merged
steipete merged 1 commit intomainfrom
refactor/acpx-command-parser
Apr 4, 2026
Merged

refactor(acpx): split Windows command parsing#60692
steipete merged 1 commit intomainfrom
refactor/acpx-command-parser

Conversation

@steipete
Copy link
Copy Markdown
Contributor

@steipete steipete commented Apr 4, 2026

Summary

  • move ACPX command parsing out of mcp-proxy.mjs into a dedicated helper module
  • add focused parser coverage in a standalone test file that runs under the extension Vitest config
  • make Windows wrapper-script policy explicit by failing fast with guidance to use cmd.exe /c, powershell.exe -File, or node <script>

Why

Follow-up to #60689.

This keeps the proxy entrypoint small, puts the Windows parser behavior under dedicated coverage, and makes unsupported direct wrapper-script spawning explicit instead of implicit.

Verification

  • pnpm exec vitest run --config vitest.extension-acpx.config.ts extensions/acpx/src/runtime-internals/mcp-command-line.test.ts extensions/acpx/src/runtime-internals/mcp-proxy.test.ts --reporter=verbose --maxWorkers=1 --no-file-parallelism
  • pnpm tsgo and pnpm build were started in this workspace, but both long-running wrappers were sticky/noisy here; the focused ACPX suite above completed cleanly

Refs #60689.

@cursor
Copy link
Copy Markdown

cursor Bot commented Apr 4, 2026

PR Summary

Medium Risk
Moderate risk because this changes how ACPX parses/spawns agent commands on Windows and now hard-fails for wrapper-script targets, which could break existing configurations if they relied on direct .cmd/.bat execution.

Overview
Refactors ACPX MCP proxy command parsing by extracting splitCommandLine into a dedicated mcp-command-line.mjs helper and simplifying mcp-proxy.mjs to import it.

Adds focused Vitest coverage for Windows path/quoting edge cases and introduces an explicit Windows policy to reject direct wrapper-script commands (.cmd/.bat/.ps1/etc.) with guidance to invoke them via cmd.exe, powershell.exe, or node. Also updates the changelog entry to reflect the new fail-fast behavior and test coverage move.

Reviewed by Cursor Bugbot for commit 4895dc6. Bugbot is set up for automated code reviews on this repo. Configure here.

@steipete steipete force-pushed the refactor/acpx-command-parser branch from 154627e to 4895dc6 Compare April 4, 2026 05:19
@steipete steipete merged commit e4dc03f into main Apr 4, 2026
1 check passed
@steipete steipete deleted the refactor/acpx-command-parser branch April 4, 2026 05:19
@steipete
Copy link
Copy Markdown
Contributor Author

steipete commented Apr 4, 2026

Landed via temp rebase onto main.

  • Gate: pnpm exec vitest run --config vitest.extension-acpx.config.ts extensions/acpx/src/runtime-internals/mcp-command-line.test.ts extensions/acpx/src/runtime-internals/mcp-proxy.test.ts --reporter=verbose --maxWorkers=1 --no-file-parallelism
  • Land commit: 4895dc6
  • Merge commit: e4dc03f

Thanks @steipete!

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 4, 2026

Greptile Summary

This PR extracts Windows command-line parsing from mcp-proxy.mjs into a dedicated mcp-command-line.mjs helper and adds focused test coverage for both modules. The refactoring is clean: the proxy entrypoint is leaner, the parser logic is independently testable, and the new assertSupportedWindowsCommand guard makes the unsupported wrapper-script case an explicit fast-fail instead of a silent spawn failure.

Confidence Score: 5/5

Safe to merge; no P0 or P1 findings.

The refactoring is clean and functionally correct. All remaining findings are P2 — a minor readability improvement to splitCommandLine and a missing session/fork test case that does not affect runtime behavior. No regressions introduced.

No files require special attention.

Comments Outside Diff (1)

  1. extensions/acpx/src/runtime-internals/mcp-proxy.test.ts, line 81-89 (link)

    P2 session/fork injection not exercised

    shouldInject in mcp-proxy.mjs treats session/fork the same as session/new and session/load, but neither this integration test nor mcp-command-line.test.ts sends a session/fork message to verify injection. A test writer could add a third write here alongside the existing session/load case to close the gap.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: extensions/acpx/src/runtime-internals/mcp-proxy.test.ts
    Line: 81-89
    
    Comment:
    **`session/fork` injection not exercised**
    
    `shouldInject` in `mcp-proxy.mjs` treats `session/fork` the same as `session/new` and `session/load`, but neither this integration test nor `mcp-command-line.test.ts` sends a `session/fork` message to verify injection. A test writer could add a third write here alongside the existing `session/load` case to close the gap.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/acpx/src/runtime-internals/mcp-proxy.test.ts
Line: 81-89

Comment:
**`session/fork` injection not exercised**

`shouldInject` in `mcp-proxy.mjs` treats `session/fork` the same as `session/new` and `session/load`, but neither this integration test nor `mcp-command-line.test.ts` sends a `session/fork` message to verify injection. A test writer could add a third write here alongside the existing `session/load` case to close the gap.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/acpx/src/runtime-internals/mcp-command-line.mjs
Line: 111-114

Comment:
**`parts.length === 0` silently no-ops when `windowsCommand` is the object branch**

When `splitWindowsExecutableCommand` returns a `{ command, args }` object, `parts` is that object and `parts.length` is `undefined` — not `0` — so the guard never fires on that code path. This is harmless in practice (the function only returns non-null on a successful regex match, guaranteeing a non-empty command), but the guard reads as if it protects both branches equally. A small clarifying comment or restructuring makes the intent explicit.

```suggestion
  const windowsCommand = splitWindowsExecutableCommand(value, platform);
  if (windowsCommand) {
    assertSupportedWindowsCommand(windowsCommand.command, platform);
    return windowsCommand;
  }
  const parts = splitCommandParts(value, platform);
  if (parts.length === 0) {
    throw new Error("Invalid agent command: empty command");
  }
  const parsed = { command: parts[0], args: parts.slice(1) };
  assertSupportedWindowsCommand(parsed.command, platform);
  return parsed;
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "refactor(acpx): split Windows command pa..." | Re-trigger Greptile

Comment on lines +111 to +114
const parts = windowsCommand ?? splitCommandParts(value, platform);
if (parts.length === 0) {
throw new Error("Invalid agent command: empty command");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 parts.length === 0 silently no-ops when windowsCommand is the object branch

When splitWindowsExecutableCommand returns a { command, args } object, parts is that object and parts.length is undefined — not 0 — so the guard never fires on that code path. This is harmless in practice (the function only returns non-null on a successful regex match, guaranteeing a non-empty command), but the guard reads as if it protects both branches equally. A small clarifying comment or restructuring makes the intent explicit.

Suggested change
const parts = windowsCommand ?? splitCommandParts(value, platform);
if (parts.length === 0) {
throw new Error("Invalid agent command: empty command");
}
const windowsCommand = splitWindowsExecutableCommand(value, platform);
if (windowsCommand) {
assertSupportedWindowsCommand(windowsCommand.command, platform);
return windowsCommand;
}
const parts = splitCommandParts(value, platform);
if (parts.length === 0) {
throw new Error("Invalid agent command: empty command");
}
const parsed = { command: parts[0], args: parts.slice(1) };
assertSupportedWindowsCommand(parsed.command, platform);
return parsed;
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/acpx/src/runtime-internals/mcp-command-line.mjs
Line: 111-114

Comment:
**`parts.length === 0` silently no-ops when `windowsCommand` is the object branch**

When `splitWindowsExecutableCommand` returns a `{ command, args }` object, `parts` is that object and `parts.length` is `undefined` — not `0` — so the guard never fires on that code path. This is harmless in practice (the function only returns non-null on a successful regex match, guaranteeing a non-empty command), but the guard reads as if it protects both branches equally. A small clarifying comment or restructuring makes the intent explicit.

```suggestion
  const windowsCommand = splitWindowsExecutableCommand(value, platform);
  if (windowsCommand) {
    assertSupportedWindowsCommand(windowsCommand.command, platform);
    return windowsCommand;
  }
  const parts = splitCommandParts(value, platform);
  if (parts.length === 0) {
    throw new Error("Invalid agent command: empty command");
  }
  const parsed = { command: parts[0], args: parts.slice(1) };
  assertSupportedWindowsCommand(parsed.command, platform);
  return parsed;
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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: 154627eea8

ℹ️ 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".

args: parts.slice(1),
};
}
import { splitCommandLine } from "./mcp-command-line.mjs";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Ship the new MCP command-line helper with proxy asset

mcp-proxy.mjs now depends on ./mcp-command-line.mjs, but the postbuild static-asset list still only copies mcp-proxy.mjs into dist (scripts/runtime-postbuild.mjs STATIC_EXTENSION_ASSETS). That means packaged runs can fail with ERR_MODULE_NOT_FOUND when ACPX launches the proxy path, because dist/extensions/acpx/mcp-command-line.mjs is never produced.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant