Skip to content

fix: wait for macOS process group reaping before runtime cleanup - #84

Merged
dobleuber merged 2 commits into
nsolid-console-7from
fix/macos-runtime-reaping
Nov 4, 2026
Merged

dobleuber merged 2 commits into
nsolid-console-7from
fix/macos-runtime-reaping

Conversation

@dobleuber

Copy link
Copy Markdown

On macOS, a killed npm process group can still contain unreaped zombies. A signal-0 group probe returns EPERM during that interval, so runtime setup prematurely reports unconfirmed termination and retains staging.

Keep polling Darwin EPERM within the existing confirmation deadline. Only ESRCH confirms that the managed group is gone; persistent probe errors retain staging and preserve their diagnostic. Other Unix probe errors and the Windows termination path keep their behavior. The tests cover transient and persistent EPERM, other-platform errors, and the emitted signal targets.

Separated from #83 so this runtime fix can merge independently of the matching N|Solid Console MCP server release.

Validation: 738 tests pass locally, plus lint, build and marketplace installation. Native Claude /review high and codex review completed on the standalone change; findings were independently checked. A minor optional test-coverage suggestion is recorded. CI runs on Linux, macOS and Windows.

@ns-control-tower

ns-control-tower commented Nov 4, 2026 •

Copy link
Copy Markdown

Walkthrough

cancelManagedTree (packages/core/src/mcp/mcp-runtime-runner.ts:196) is the Unix timeout-cancel path for the MCP runtime's npm installer: SIGTERM → grace → SIGKILL → await root close → poll the detached process group with signal 0 until it is gone. The old loop treated the first non-ESRCH probe error as a failed check — on macOS, though, a killed group can still hold unreaped zombie members, and XNU skips SZOMB members when probing a group, answering EPERM until they are reaped. Runtime setup therefore reported unconfirmed termination (and retained staging) while the group was actually gone.

The fix keeps polling Darwin EPERM within the existing 5 s confirmation deadline, preserving the diagnostic if it persists (process group check failed (EPERM)); only ESRCH confirms. Non-Darwin probe errors and the Windows branch keep their previous behavior. The tests add transient/persistent Darwin EPERM, Linux EPERM and Darwin EACCES cases.

overview
termination-sequence

Assessment

  • 🛠️ Non-blocking: inline suggestion on packages/core/test/unit/mcp/mcp-runtime-runner.test.ts:104 — the "probe keeps succeeding to the deadline" branch that emits managed npm process group still exists after the confirmation deadline is the one path in the rewritten loop with no test; a verified test case is provided there.
  • Validation: CI run 41913586320 on eb30b9a2 is green on ubuntu / macos / windows (lint, build, test, marketplace install). Locally: 8/8 targeted tests pass on this head; the new Darwin transient-EPERM case fails on base 7ab7b66 — the fix is genuinely pinned.
  • Scope: process-termination reliability only — no auth, data, CI-workflow, or security-control changes; no human escalation required.

Verdict: APPROVE — correct, fail-closed Darwin EPERM→ESRCH handling; one optional test-coverage suggestion, no blocking findings.

@dobleuber
dobleuber added this pull request to stack #85 November 4, 2026 21:11
@dobleuber
dobleuber marked this pull request as ready for review November 4, 2026 21:25
@dobleuber dobleuber self-assigned this Nov 4, 2026
assert.match(outcome.error ?? '', new RegExp(`process group check failed \\(${code}\\)`))
})
}
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion — packages/core/test/unit/mcp/mcp-runtime-runner.test.ts:104

The rewritten poll loop has one branch the new suite never reaches: a group probe that keeps succeeding (a live member survives) right up to the deadline, which exits through the probeError ?? '…still exists…' fallback at packages/core/src/mcp/mcp-runtime-runner.ts:268. Every new test throws on signal === 0, so the success path and its operator-facing diagnostic are unverified — a future edit could drop that classification without CI noticing. Optional and non-blocking; the case below is verified to pass on this head.

Suggested change
})
it('keeps polling a surviving group and reports it as still existing at the deadline', async (t) => {
t.mock.method(process, 'kill', () => true) // the group keeps answering: a member survives
const outcome = await cancelManagedTree({ pid: 1234 }, Promise.resolve(), {
platform: 'darwin',
confirmMs: 50,
})
assert.deepStrictEqual(outcome, {
confirmed: false,
error: 'managed npm process group still exists after the confirmation deadline',
})
})
})
🤖 Ask ns-control-tower to fix this

@ns-control-tower please fix: add the surviving-group probe case so the still exists after the confirmation deadline fallback in cancelManagedTree has test coverage.

@brycebaril brycebaril left a comment

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.

Thanks, this is the macOS fix from #83, and its code and tests match what I reviewed there. On macOS, probing a process group whose members are all zombies returns EPERM until they're reaped. The fix keeps polling within the same deadline, and only ESRCH confirms. A persistent EPERM keeps its diagnostic. The tests cover transient and persistent EPERM, and other platforms' errors.

It fixes the macOS failure that also hit main on 10-26 and #78, so it can merge now.

@dobleuber
dobleuber merged commit 58cd2bc into nsolid-console-7 Nov 4, 2026
5 checks passed
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.

3 participants