Skip to content

fix(hooks): check the command line of Linux node processes named MainThread - #1051

Merged
rullerzhou-afk merged 4 commits into
mainfrom
fix/shared-cmdline-mainthread
Sep 25, 2026
Merged

rullerzhou-afk merged 4 commits into
mainfrom
fix/shared-cmdline-mainthread

Conversation

@rullerzhou-afk

@rullerzhou-afk rullerzhou-afk commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Problem: createPidResolver() in hooks/shared-process.js runs an adapter's agentCmdlineCheck only on ancestors whose process name is in agentCmdlineNames, and the default was node / node.exe. Node names its main thread MainThread from 23.8 (src,lib: use uv_thread_setname to a better multi-thread debugging nodejs/node#56416, so all of 24.x) and node-MainThread from 25.5 (src: use node- prefix on thread names nodejs/node#61307), and on Linux ps -o comm= reports the main thread's name. On Linux with current Node, an agent running as a node process that never set process.title was therefore never checked and got no agent pid. fix(minimax): #1038 follow-ups — ownership marker, staged install, honest uninstall, CLI agent pid #1049's Linux CI hit this for MiniMax.
  • Fix: the default is now the exported, frozen DEFAULT_AGENT_CMDLINE_NAMES: node, node.exe, mainthread, node-mainthread. The adapters that rely on the default get it with no change: Claude, Copilot, Gemini, Kimi, Qwen Code, QwenWork, Qoder, QoderWork and Antigravity.
  • Adapters with their own list: ZCode now spreads the default into its list. WorkBuddy keeps its electron-only probe, because its check only matches the bundled macOS .app task runner.
  • Guard: a hook that passes its own agentCmdlineNames must write agentCmdlineNames: new Set([..., ...DEFAULT_AGENT_CMDLINE_NAMES]), with only plain string literals and the spread itself inside the brackets. A test scans hooks/*.js and fails on any other form, such as a hand-written, trimmed or shorthand list. AGENTS.md documents the rule.
  • MiniMax: fix(minimax): #1038 follow-ups — ownership marker, staged install, honest uninstall, CLI agent pid #1049 landed first with MiniMax's own copy of the same four names. After syncing with main, 67f9fe32 drops that copy so MiniMax uses the shared default. Its test now asserts that the resolver options carry no list of their own and still wire isMinimaxAgentCommandLine.
  • macOS and Windows behave as before: there ps -o comm= reports the executable path and Win32_Process reports the image name.

Validation

  • related suites (38 files: shared-process, the pid resolver and cache suites, and every adapter hook/install suite): 1,155 tests, 0 failed (before syncing with main)
  • after syncing with main (fix(minimax): #1038 follow-ups — ownership marker, staged install, honest uninstall, CLI agent pid #1049 merged): the same 38 files pass 1,161 tests with 0 failed; npm test gives 11,154 tests, 11,108 passed, 46 skipped, 0 failed (main at 28b09e8c: 11,145 tests, 0 failed, 46 skipped)
  • mutation check: 19 targeted mutations, all caught. They revert the default, drop each name, restore ZCode's old list, try hand-written, shorthand, trimmed, string and call-argument lists, drop MiniMax's agentCmdlineCheck, and keep COLUMNS under a procps-like ps. Three controls for legitimate list forms pass.
  • Node's source at each tag: v23.7.0 sets no name; v23.8.0, v24.0.0, v24.18.0, v24.21.0 and v25.4.0 set MainThread; v25.5.0 and v26.10.0 set node-MainThread. libuv's Linux thread-name buffer is 16 bytes, so the 15-character name is not truncated.
  • four rounds of independent review, the last covering the MiniMax commit and the merge with main. The final commit (015b7aaa), which narrows the list scan to string literals plus the spread, was not re-reviewed.
  • Linux is verified by CI, not on a desktop machine. The new real-process test runs a plain node child that never set process.title through the real ps. On ubuntu-latest with Node 24.18.0 it passed at 67f9fe32, as did MiniMax's node-named mcode end-to-end test. No real agent was run on a Linux desktop, and node-MainThread (Node ≥ 25.5) is checked only against Node's source and mocked tests.

Known limitations and follow-ups

  • Linux now runs command-line checks on untitled node ancestors, as macOS and Windows already did. Kimi's check (includes("kimi")) is loose, so a nearer unrelated node process whose command line contains "kimi" can take the agent pid. That was already true on macOS and Windows, and Kimi Code's own hook runner has no such intermediate process. Tightening it is a separate task, along with two other Kimi name issues: Kimi Code retitles itself kimi-code, which is not in Kimi's name list, and the mixed-case Kimi Code entry can never match.
  • The resolver reads command lines with ps -o command=. procps applies an exported COLUMNS as the width of piped output, so a hook environment with COLUMNS set can truncate a long command line. This predates the change and is not addressed here.

Refs #1049

…Thread

createPidResolver runs an adapter's agentCmdlineCheck only for ancestors
whose process name is in agentCmdlineNames, and the default was node /
node.exe. Node names its main thread "MainThread" from 23.8 (nodejs/node#56416,
so all of 24.x) and "node-MainThread" from 25.5 (nodejs/node#61307), and on
Linux `ps -o comm=` reports the main thread's name. So on Linux with current
Node, an agent running as a node process that never set process.title was
never checked, and adapters relying on the default got no agent pid there.

- Export the default as DEFAULT_AGENT_CMDLINE_NAMES: node, node.exe,
  mainthread, node-mainthread.
- ZCode spreads it into its own list. WorkBuddy keeps its electron-only
  probe, which only matches the bundled macOS .app task runner.
- Tests: the default names, a mocked Linux walk for each name, a real node
  child that never set process.title (so Linux CI notices a future rename),
  and a scan that fails when a hook passes its own list without spreading
  the default.
DEFAULT_AGENT_CMDLINE_NAMES now covers Node's Linux main-thread names, so
MiniMax's own AGENT_CMDLINE_NAMES (the same four names) is a hand copy,
and the agentCmdlineNames scan rejects it. Drop it and let MiniMax follow
the shared default. Its test now asserts that the resolver options carry
no list of their own and still wire isMinimaxAgentCommandLine; the
node-named mcode end-to-end test keeps covering the real behavior on
Linux CI.
…ineNames

The scan split the Set's contents on every comma, so a spread inside a
call argument, as in new Set([String("zcode", ...DEFAULT_AGENT_CMDLINE_NAMES,
"x")]), read as an element of its own and passed although the Set then
held only "zcode". Accept one shape instead: the brackets may hold plain
string literals and ...DEFAULT_AGENT_CMDLINE_NAMES itself, nothing else.
Also stop claiming that every mention in a comment is reported; a mention
is reported whenever its text is not in that shape.
@rullerzhou-afk
rullerzhou-afk merged commit fcd17da into main Sep 25, 2026
21 checks passed
@rullerzhou-afk
rullerzhou-afk deleted the fix/shared-cmdline-mainthread branch September 25, 2026 05:29
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