Repository navigation
Description
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
clilayer slot (parses againstcatalog/cli-reference.jsonto map flag → settings key, parallel to howcatalog/env-settings-map.jsonmaps 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
envsettings 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.
- Snapshot-at-exec-time. Process env and argv are frozen when
execis 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." - Process discovery is non-trivial. "The claude process" is ambiguous: zero, one, or many
claude/claude-codeprocesses 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. - 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.
- Dotenv files still invisible. Even with process-env reading,
.envfiles claude reads at startup are merged into its env internally via dotenv libraries — they show up inenvirononly 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." - 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").
- Windows is harder than Unix (see proposals).
- Capability surface impact. Adds the
sysinfoRust 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
argvthrough a new flag → settings-key parser (parallel toenv-settings-map.json— call itcli-settings-map.json) and emit a realclilayer 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()usesKERN_PROCARGS2sysctl. Works for processes the calling user owns. No root needed. - Linux: reads
/proc/<pid>/environ. Works for owned processes. - Windows:
sysinfoexposescmd()(argv) butenviron()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
clilayer slot, closing a long-standing inventory gap.
Cons:
sysinfopulls 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
sysinfoalready 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
/procon Linux,proc_listpidson 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
.envwithout exporting. - Polling cadence beyond user-triggered refresh (the file-watcher beat may be enough).
- Auto-mapping every flag in
catalog/cli-reference.jsonto settings keys — start with the unambiguous handful (--model,--permission-mode, etc.) parallel to howenv-settings-map.jsonstarted with 8 mappings.
Related
spec/settings-display.md:253— "Resolving CLI flags of a runningclaudeprocess" 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.
Activity
Cross-reference #11 from spec for runtime introspection
Grey out project/project_local in the precedence rail (#12)
Sync docs with the cli (#11) and project (#12) coverage gaps
samkeen commentedon May 13, 2026
Resolved by #15.
Both halves landed:
cli precedence slot —
runtime.rsreads attached argv viasysinfo;cli_layer.rsparses it against a newcatalog/cli-settings-map.json(5 starter flags:--model,--permission-mode,--effort,--agent,--add-dir) and emits a realcliLayerRead. The rail row populates when the attached process has any mapped flag in its argv.EnvVarsPanel ground truth — the panel grows an
attachedcolumn alongsideshell. When attached, the env precedence layer also reads the attached claude's environ viaread_env_layer_attached(environ)rather than knobs.cc's own. Per-rowΔbadge surfaces when the attached value and shell value disagree; newattached/Δ difffilter chips appear only when attached.Implementation contract:
spec/attach-mode.md. Went with Proposal A (sysinfoUnix-first) as recommended in the issue body.Correction: the dotenv claim in the issue body is wrong.
I smoke-tested this before implementing and the claim doesn't hold. Setup: a throwaway dir with
.envcontainingDOTENV_SMOKE_VAR=hello-from-dotenv-file, the variable not exported in the parent shell. Then ranclaude -p --allowedTools 'Bash' --output-format text 'Use the Bash tool to run: printenv DOTENV_SMOKE_VAR ; echo --EXIT $?'from inside that directory.Result:
printenvexited 1 with no output — claude's Bash tool did not see the var. Positive control with the same var exported in the parent shell did show through (hello-from-shell-export, exit 0), confirming the test methodology.The two env-into-claude paths I can find documented (per
code.claude.com/docs/en/settings.mdandenvironment-variables.md) are:envblock insidesettings.jsonfiles.envmentions in the upstream docs are all about permission rules (e.g.Read(./.env)as an example of denying claude's Read tool access to dotenv files) — the opposite mechanism. The propagated "claude reads dotenv" claim came from one footnote and spread to four spec spots + the issue body; all corrected onmainas part of this PR. The "dotenv files visible only to claude" gap simply doesn't exist.Tested with claude 2.1.140 on macOS. If anyone has a counter-case (different platform, interactive session, plugin / hook behaviour), please flag it.
Merge pull request #15 from AlteredCraft/feat/attach-mode
samkeen commentedon May 16, 2026
Shipped on
feat/attach-mode— see commits f49836c ("Ground inspection in a chosen claude session (#11, #12)") and 0a5ac27 (PR #15 merge). The runtime introspection layer reads cwd/argv/environ for same-UID claude processes via theread_runtime_layerTauri command, exposed in the UI through the SessionPill 4-state picker. cli-settings-map.json (5 starter flags) projects attached argv onto the cli precedence layer; attached environ feeds the env layer.