Skip to content

Read another running claude process's argv + env (the "runtime introspection" layer) #11

@samkeen

Description

@samkeen

Problem

knobs.cc inspects the static sources of Claude Code's configuration — files on disk, OS-policy stores, and the user's own shell environment as projected through catalog/env-settings-map.json. It does not inspect what a running claude process actually sees.

That gap shows up in two places today:

1. Settings-precedence: the cli layer slot is permanently empty

spec/inventory.md:50 defines the layer precedence as managed > cli > env > project_local > project > user > default. Layer 6 (cli) is the layer that wins when a claude invocation passes flags that override settings — e.g. claude --model opus, claude --permission-mode auto, or any flag in catalog/cli-reference.json (20 commands + 63 flags) that maps to a settings key. The slot exists in the type system; the rail renders an empty-state row; nothing populates it because there's no way to know what flags were passed without reading some other process's argv. Listed today as out-of-v1 in spec/settings-display.md:253 ("Resolving CLI flags of a running claude process").

2. EnvVarsPanel: shell env can diverge from what claude actually inherited

EnvVarsPanel (shipped 2026-05-10) reads knobs.cc's own process env via the read_shell_env_vars Tauri command and surfaces those values per env-var catalog entry. The panel's footnote (src/components/inspector/EnvVarsPanel.tsx:550-559) flags the limitation:

Shell values reflect the environment knobs.cc was launched with — usually the same env Claude Code would inherit from your shell, but Finder/Spotlight launches use LaunchServices' env, which can differ.

The most common divergence: a user has ANTHROPIC_API_KEY exported in ~/.zshrc, runs claude from a terminal (rich shell env), and launches knobs.cc from Finder (sparse LaunchServices env). The two processes started with different envs, and knobs.cc shows its own — not what claude actually has.

A second divergence: Claude Code reads dotenv files (.env) at startup that knobs.cc has no visibility into, also called out in the footnote.

A third, subtler one: env vars set or unset after claude was exec'd don't reach claude (process env is frozen at exec time), so what the user sees in their current shell may not match what claude has either. Reading claude's actual process env is the only way to ground-truth this.

Unified shape

Both gaps are answered by the same capability: reach into a running claude process and read its argv + env. Worth treating as one feature — the "runtime introspection" layer — rather than two separate efforts:

  • argv → populates the cli layer slot (parses against catalog/cli-reference.json to map flag → settings key, parallel to how catalog/env-settings-map.json maps env → settings key today).
  • environ → either replaces or supplements the EnvVarsPanel's shell column ("shell as knobs.cc sees it" vs. "shell as the running claude sees it"), and feeds the existing env settings layer.

Same OS APIs, same process-discovery problem, same UX questions (which claude do you mean if multiple are running?). Splitting them would duplicate the discovery + permission work.

Known limitations

These bound the design space — none of them are blockers, but they shape what the feature can promise.

  1. Snapshot-at-exec-time. Process env and argv are frozen when exec is called. Env vars set in the user's shell after claude started don't propagate to claude. This is a real semantic, not a knobs.cc bug — but the UI has to be honest about "what claude sees right now" being a snapshot of "what claude was started with."
  2. Process discovery is non-trivial. "The claude process" is ambiguous: zero, one, or many claude/claude-code processes can be running, possibly under different users, possibly inside containers, possibly remote (SSH session). v1 should target single-user, local, foreground processes — ambiguity gets a picker.
  3. Permission boundaries. Reading another process's env requires the calling process to share the user UID. Reading a root-owned claude (rare but possible) won't work without elevation, and we shouldn't try.
  4. Dotenv files still invisible. Even with process-env reading, .env files claude reads at startup are merged into its env internally via dotenv libraries — they show up in environ only if the dotenv loader exports them. Some don't (they keep them in-memory). Honest UI: "this is the kernel-visible process env, dotenv may add more."
  5. Container/sandboxed claude. A claude running inside a Docker container or macOS sandbox has env isolated from the host — knobs.cc on the host can't see it. Out of scope; should fail gracefully ("no claude processes detected").
  6. Windows is harder than Unix (see proposals).
  7. Capability surface impact. Adds the sysinfo Rust crate dep; arguably also a new "opt-in to runtime introspection" user gesture so we're not silently scanning processes. Doesn't expand the Tauri JS-side capability surface — all Rust.

Proposals

Proposal A — sysinfo crate, Unix-first (recommended starting point)

Use the sysinfo crate from Rust to enumerate processes named claude / claude-code, then read each one's argv and environ. New Tauri command read_runtime_layer() -> RuntimeSnapshot returns:

struct RuntimeSnapshot {
    processes: Vec<ClaudeProcess>,  // 0..N
    selected_pid: Option<u32>,      // user pick, or auto if exactly one
    error: Option<String>,
}

struct ClaudeProcess {
    pid: u32,
    started_at: SystemTime,
    cwd: PathBuf,
    argv: Vec<String>,
    environ: HashMap<String, String>,
}

Frontend wiring:

  • Settings precedence: feed argv through a new flag → settings-key parser (parallel to env-settings-map.json — call it cli-settings-map.json) and emit a real cli layer entry, populating the rail slot that's been empty since v1.
  • EnvVarsPanel: add a per-row column for "running claude's env" alongside the existing "shell" column. When a value differs, show a small Δ chip — that's the actual user pain point.

OS-specific notes:

  • macOS: sysinfo::Process::environ() uses KERN_PROCARGS2 sysctl. Works for processes the calling user owns. No root needed.
  • Linux: reads /proc/<pid>/environ. Works for owned processes.
  • Windows: sysinfo exposes cmd() (argv) but environ() returns empty on Windows. See Proposal C for the gap.

Pros:

  • Single dep covers argv + env on Unix.
  • One Rust module; no new Tauri JS-side capabilities.
  • Keeps the existing "file watcher emits an event, frontend re-runs" pattern — runtime layer can refresh on the same beat (or its own pollable command).
  • Clear path to populate the cli layer slot, closing a long-standing inventory gap.

Cons:

  • sysinfo pulls in a small but non-trivial transitive tree.
  • Process enumeration has a per-tick cost; needs throttling ("refresh runtime layer every N seconds, or on user request").
  • Doesn't solve Windows; that's deferred per scope.

Proposal B — direct OS calls, no sysinfo (smaller, more code)

Skip sysinfo and call KERN_PROCARGS2 directly on macOS, read /proc/<pid>/environ directly on Linux. About 200 lines of unsafe Rust per platform vs. a 5-line crate use.

Pros:

  • No new dep.
  • Smaller binary.
  • Total control over the data shape.

Cons:

  • Reinventing what sysinfo already does correctly.
  • macOS sysctl ABI is fiddly; getting the buffer-sizing right is the source of most of the bugs in sysinfo's history.
  • We'd own the process-enumeration code (walking /proc on Linux, proc_listpids on macOS) too.

Not recommended unless dep weight becomes a measurable problem.

Proposal C — Windows via NtQueryInformationProcess (deferred)

For Windows, sysinfo doesn't expose process env. The supported approach is NtQueryInformationProcess(ProcessBasicInformation) to get the PEB address, then ReadProcessMemory to walk to RTL_USER_PROCESS_PARAMETERS::Environment. Requires PROCESS_QUERY_LIMITED_INFORMATION + PROCESS_VM_READ rights. Real work; would need its own Tauri command + tests + a Windows CI runner that already exists.

Per the issue ask, this is deferred until Unix lands and proves out. The Windows version of knobs.cc would render the runtime layer with a "not supported on this platform" empty state until then — same posture as the macOS-only plist reader vs. the Windows-only winreg reader (precedent set in Phase 6).

Proposal D — don't ship runtime introspection; use a CLI sidecar (alternative framing)

Have Claude Code itself emit a ~/.claude/runtime/<pid>.json snapshot on startup (or on-demand via claude doctor-style command), and have knobs.cc read those files. Punts cross-process introspection back to upstream.

Pros:

  • No sysinfo / no platform-specific code in knobs.cc.
  • Same read posture as today (files on disk).
  • Works on Windows for free.

Cons:

  • Requires upstream cooperation; out of our control.
  • Doesn't exist; would need a feature request + design alignment with the Claude Code team.
  • Files-on-disk lag behind the actual process state.

Worth flagging in case the conversation with upstream is easy. Not a recommendation.

Recommendation

Proposal A, Unix-first. Land macOS + Linux behind a new read_runtime_layer command, wire it into both the cli precedence slot and the EnvVarsPanel as a sister column. Defer Windows until the Unix version proves the UX is worth it. Revisit Proposal D only if upstream contact happens organically.

Out of scope (until the Unix version lands and is exercised)

  • Windows support (Proposal C).
  • Containerised / sandboxed claude detection.
  • Dotenv file inspection for processes that load .env without exporting.
  • Polling cadence beyond user-triggered refresh (the file-watcher beat may be enough).
  • Auto-mapping every flag in catalog/cli-reference.json to settings keys — start with the unambiguous handful (--model, --permission-mode, etc.) parallel to how env-settings-map.json started with 8 mappings.

Related

  • spec/settings-display.md:253 — "Resolving CLI flags of a running claude process" listed as out of v1.
  • spec/inspector-ui.md:202-205 — EnvVarsPanel footnote caveat.
  • spec/roadmap.md § "Deferred plan" — currently points at a stale local plan path (~/.claude/plans/1-...-seahorse.md); replace with this issue.
  • catalog/cli-reference.json — the catalog the eventual flag → settings-key mapping will draw on (load-bearing once this lands; see Surface synced catalogs that have no UI consumer yet #10).
  • catalog/env-settings-map.json — the parallel mapping pattern that already shipped for env.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions