Skip to main content

Module processes

Module processes 

Source
Expand description

OS-level process introspection for the Inspector pane’s PROCESSES section.

Walks the descendant tree of the spawned claude binary at the operating-system level via the sysinfo crate, returning a sorted snapshot of “interesting” descendants: shells, compilers, task runners, monitors - anything claude’s tool flow spawned that’s still alive at scan time.

Why not key off the wire alone? The wire’s task_started / task_updated pair covers Bash with run_in_background:true, a foreground call that outlives a beat, and the Monitor tool’s streaming watcher, but it doesn’t surface:

  • A foreground Bash that ends within a beat, which leaves no frame.
  • Grandchild processes the spawned shell forks off (e.g. a cargo build that fans out into rustc workers).
  • Anything that detaches from claude’s tool registry but stays in the process tree.

macOS and Linux are first-class, via sysinfo. Windows falls back to an empty snapshot: shell-process names (cmd.exe / powershell.exe) need a recogniser nobody has asked for yet.

The snapshot is consumed by forge-workspace’s seat walk (thin mediator), which passes the session’s live background commands through so the scan can adopt matching OS processes by cmdline lookup (a backgrounded local_bash sits outside claude’s tree).

Re-exports§

use std::time::Duration; 🔒
use std::time::SystemTime; 🔒
use forge_primitives::McpServerStatus; 🔒
use sysinfo::Pid; 🔒
use sysinfo::ProcessRefreshKind; 🔒
use sysinfo::ProcessesToUpdate; 🔒
use sysinfo::System; 🔒

Structs§

ConfiguredMcpServer
A configured MCP server distilled to what the process matcher needs: the configured name plus the stdio launch command + args used to match it against a live process cmdline.
InfraLabel
Friendly display name + kind for a recognized known-infra process.
ProcessEntry
One descendant process the scanner found alive. Field shape is driven by what a view’s process row builder needs (PID + cmdline + memory for the metadata line) + the cmdline-matching needed for wire-tracked overlay (process_cmdline_matches_tool_input).
ProcessSnapshot
Snapshot of claude’s descendant processes at one point in time. Always succeeds - failures (sysinfo errors, process gone) collapse to an empty processes vec.
RootProcess
One depth-0 process the elimination join may consider, with whether an alive wire tool call already claims it.

Constants§

INTERPRETERS 🔒
Executables an stdio MCP server’s live process image runs under, matched on the argv[0] basename so a server that execed out of its configured sh -c wrapper still qualifies.
SCAN_STALENESS
How old a snapshot may be before another walk replaces it.

Functions§

basename_exe
Strip the executable’s directory from a cmdline headline so the row reads cargo nextest run, not /opt/homebrew/bin/cargo nextest run. Only the FIRST token (the executable) is basenamed; args are kept verbatim (they carry the distinguishing detail). A first token with no / (e.g. a postgres: walwriter process title) is unchanged.
classify_known_infra
Friendly name for a known-infra process (MCP servers today), derived from its cmdline. None when unrecognized.
cmdline_haystack
The normalized cmdline text a process is matched on: the unwrapped inner command for a shell wrapper, else the cmdline itself.
cmdline_needle
The normalized command text a wire-tracked tool call is matched on. normalize_ws already trims, so a whitespace-only command yields the empty string and the caller can treat that as “no needle”.
configured_mcp_servers
Extract the stdio-launched configured MCP servers from an mcp-status snapshot. A server contributes (name, command, args) only when its config blob carries a string command (the stdio shape); url-only (http/sse) servers have no local process, so they’re skipped.
configured_server_matches 🔒
True when haystack (a whitespace-normalized process cmdline) contains every match token of server. Leading npx/npm passthrough flags (e.g. -y) are dropped first: npx -y <pkg> re-execs into npm exec <pkg> with the flag stripped, so the tokens are the package spec (the first non-flag arg) plus everything after it - where the real distinguisher --cdp-endpoint <url> lives and survives into the process cmdline. Empty / all-flag args fall back to the bare command. Substring containment tolerates the process-side @version suffix.
configured_text_match
The configured server whose command + args uniquely text-match cmdline. None when none matches or several do - the ambiguous case declines rather than guesses.
elect_unmatched_server
Pair the sole unmatched Connected stdio server with the sole unmatched candidate process, returning that process’s pid and the name its row takes.
extract_inner_command
Unwrap a shell-wrapper cmdline to the user command inside it. Recognizes the shape claude wraps Bash tool calls in (captured via ps -axww): /bin/zsh -c source <snapshot> ... && eval '<CMD>' < /dev/null && pwd -P >| /tmp/claude-<hash>-cwd. The inner command is single-quoted between eval ' and ' < /dev/null. Returns None when cmdline is not a recognized wrapper (non-shell processes pass through unchanged at the call site).
include_subtree 🔒
Insert every descendant of root (not root itself) into included, walking the parent -> children index.
is_elimination_candidate 🔒
True when a root process may stand in for an unmatched server: its argv[0] is one of INTERPRETERS. Keeps transient tool processes (an rg from a Grep) out of the pool, which would otherwise decline the pairing on any scan where one was alive and flicker the row.
live_candidates 🔒
Build a ProcessEntry for every live, interesting process in the table, dropping the scanner’s own pid, zombies/dead, and the one-shot ps / sysinfo self-probes.
mcp_friendly_name 🔒
Friendly MCP name from a parsed (scope, name), or None when the package isn’t a recognized MCP server:
mcp_name_from_node 🔒
node /opt/foo-mcp-server.js → foo. Requires a node argv[0] and a later token whose basename (sans .js / @version) resolves via mcp_friendly_name (scope-less, so a bare mcp doesn’t match).
mcp_name_from_npm 🔒
npm exec @playwright/mcp@latest → playwright. Requires the literal npm + exec argv prefix; the package spec must resolve to an MCP name via mcp_friendly_name or this returns None.
normalize_ws 🔒
Collapse every run of ASCII whitespace (spaces, tabs, newlines) to a single space and trim the ends. sysinfo’s proc.cmd() joins argv with single spaces, so a multi-line / multi-space wire command never matches the captured cmdline byte-for-byte; normalizing both sides first makes the substring check robust.
parse_npm_pkg 🔒
Parse an npm package spec into (scope, name) with the @version stripped. @playwright/mcp@latest -> (Some("playwright"), "mcp"); @upstash/context7-mcp -> (Some("upstash"), "context7-mcp"); context7-mcp@1.0 -> (None, "context7-mcp").
process_cmdline_matches_tool_input
True when process_cmd plausibly matches the shell command captured in a wire-tracked tool’s raw_input.command. Used by a view’s process row builder (path A+) to overlay wire-tracked task descriptions onto OS-detected processes when both refer to the same work.
resolve_infra_label
Friendly label for a known-infra process: the CONFIGURED MCP server name when the process’s command + args uniquely match one of configured, else the package-derived name from classify_known_infra. None when neither produces a name - the process isn’t a recognized MCP server.
scan
Walk claude_pid’s descendant tree plus any command-matched backgrounded local_bash and build a sorted snapshot. Always returns successfully - sysinfo failures, missing processes, or empty trees all collapse to an empty snapshot with a fresh scanned_at.
select_snapshot 🔒
Select the processes that belong in the snapshot: the descendants of claude_pid, plus any candidate whose cmdline matches one of extra_commands together with ITS descendants (a backgrounded local_bash living outside claude’s tree). Pure over candidates so the adoption logic is unit-testable without a live process table.
strip_plugin_namespace
The bare server name inside a plugin-namespaced configured name. The CLI exposes a plugin-provided MCP server as plugin:<plugin>:<server>, which reads worse than the package-derived name; anything else (including a plain name that merely contains a colon) is returned verbatim.