Skip to content

test(hooks): add cross-platform regression matrix for MCP readiness#354

Open
sebastianbreguel wants to merge 5 commits intomksglu:nextfrom
sebastianbreguel:test/mcp-ready-regression-matrix
Open

test(hooks): add cross-platform regression matrix for MCP readiness#354
sebastianbreguel wants to merge 5 commits intomksglu:nextfrom
sebastianbreguel:test/mcp-ready-regression-matrix

Conversation

@sebastianbreguel
Copy link
Copy Markdown
Contributor

What / Why / How

What. Adds tests/hooks/mcp-ready.test.ts — a dedicated regression matrix for hooks/core/mcp-ready.mjs, the module that #347 rewrote.

Why. PR #347 fixed the PPID-bypass on WSL2 / bash -c "node ..." by switching from a process.ppid-keyed sentinel lookup to a directory-scan with kill(pid, 0) liveness probes. The 11 test files touched in #347 updated the sentinel path to use process.pid, but none of them assert the load-bearing contract: that isMCPReady() returns true when the only live sentinel was written by a process whose PID is outside the test runner's process tree. A future refactor that re-introduces PPID coupling, hardcodes a different temp root, or removes the stale-cleanup branch would not be caught by current CI.

How. One new file, three describe blocks, no production code changes:

  1. mcp-ready: contract — locks in sentinelPathForPid shape, deprecated sentinelPath backward-compat (still keyed by process.ppid for one release cycle per the JSDoc), sentinelDir() platform branch (/tmp on Unix, os.tmpdir() on win32), happy path, and resilience to malformed payloads (empty, non-numeric).
  2. mcp-ready: stale-cleanup self-healingisMCPReady() unlinks dead-PID sentinels (uses INT32_MAX as a guaranteed-dead PID, no spawn). Gated by describe.skipIf(POLLUTED) to skip locally when an unrelated MCP server's live sentinel exists in /tmp (iteration order would make assertions flaky); CI runners are clean and run these.
  3. mcp-ready: PPID-independence (regression for #347) — spawns a child Node process whose PID ∉ {process.pid, process.ppid}, has it write its own sentinel via the same path shape src/server.ts uses, then asserts isMCPReady() === true. This is the test that, if removed, allows Regression in #230: PPID mismatch — mcpRedirect swallows all redirects when hook spawned via shell wrapper #347 to silently regress.

Affected platforms

  • ubuntu-latest
  • macos-latest
  • windows-latest

Cross-platform notes:

  • Uses path.join throughout, no hardcoded / separators.
  • The sentinelDir() test mocks process.platform via Object.defineProperty(..., { configurable: true }) and restores in afterEach.
  • The unreadable-file edge case is omitted (would require chmod, which is a no-op on Windows).
  • Child-spawn uses process.execPath + -e inline script for portability.

Test plan

  • npx vitest run tests/hooks/mcp-ready.test.ts passes locally on macOS (8 passed, 2 skipped under POLLUTED gate; in clean envs the 2 skipped also pass).
  • npx tsc -b --noEmit passes.
  • No production source changes; bundles untouched.
  • CI matrix on this PR confirms ubuntu/macos/windows × Node 20 all pass.

Checklist

  • Tests added (TDD: red → green); no implementation changes
  • npm test passes
  • npm run typecheck passes
  • Docs updated if needed — N/A (test-only)
  • No Windows path regressions (forward slashes only)
  • Targets next branch

Locks in the directory-scan + PID-liveness contract from mksglu#347. The 11
test files updated by mksglu#347 changed the sentinel path but never asserted
that isMCPReady() returns true for a sentinel whose PID is outside the
test runner's process tree — the exact condition the PPID-keyed lookup
failed on under WSL2 / `bash -c "node ..."` topologies.

Coverage:
- sentinelPathForPid + deprecated sentinelPath shape
- sentinelDir platform branch (/tmp on Unix, os.tmpdir on win32)
- isMCPReady happy path + resilience to malformed payloads
- Stale-sentinel self-healing (gated to clean envs)
- PPID-independence regression: child PID ∉ runner tree → still true

Pure test-only PR. No production changes.
…h, env-var path)

- Drop sentinelPath() deprecated-export test. The JSDoc says it's kept for
  one release cycle; testing what's about to die is a maintenance trap.
- Collapse empty-payload + non-numeric-payload tests into a single it.each.
  Same contract, fewer lines.
- Pass resolved sentinel directory to the regression child via env var
  instead of recomputing the platform branch inline. Keeps mcp-ready.mjs
  as the single source of truth for the path shape.
@mksglu
Copy link
Copy Markdown
Owner

mksglu commented Apr 27, 2026

Hi @sebastianbreguel! Thanks for the PR. Please use existing Unit Test file instead of new one.

Per review feedback: move the three describe blocks (`contract`,
`stale-cleanup self-healing`, `PPID-independence`) from the standalone
tests/hooks/mcp-ready.test.ts into tests/hooks/core-routing.test.ts as
top-level describes after the existing `routePreToolUse` block. Same
test bodies, same assertions; only the host file changed.

The stale-cleanup block adds a local `beforeEach` that unlinks the
file-level `mcpSentinel` so the runner's own live sentinel does not
mask the dead-PID cleanup the test verifies.
@sebastianbreguel
Copy link
Copy Markdown
Contributor Author

Done — moved the three describe blocks into tests/hooks/core-routing.test.ts (after the existing routePreToolUse block) and deleted tests/hooks/mcp-ready.test.ts. The stale-cleanup block has a local beforeEach that unlinks the file-level mcpSentinel so the runner's own live sentinel doesn't mask the dead-PID cleanup the test verifies. New commit pushed.

Merge imports from both branches and fix sentinelDir tests to
temporarily clear env override when testing platform defaults.
@sebastianbreguel
Copy link
Copy Markdown
Contributor Author

Resolved merge conflict with next — merged imports and fixed sentinelDir tests to clear env override when testing platform defaults. Ready for review.

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.

2 participants