From c848eeb76875acadffd131e3dd220ed1fd205e70 Mon Sep 17 00:00:00 2001 From: bellman Date: Fri, 5 Jun 2026 10:15:23 +0900 Subject: [PATCH] fix: status JSON reports all workspace panes not just the first (#326) SessionLifecycleSummary now collects all matching tmux panes into an all_panes field and includes them in the JSON output. Previously the status command returned on the first non-idle pane, losing all other active panes in the same workspace/session. Generated with https://github.com/Yeachan-Heo/gajae-code Co-authored-by: Gajae Code --- ROADMAP.md | 2 +- rust/crates/rusty-claude-cli/src/main.rs | 44 ++++++++++++++++++------ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index b931161f..d1532d84 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6265,7 +6265,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 325. **DONE — `help --output-format json` returns valid JSON but hides the actual help schema inside one prose `message` string** — dogfooded 2026-04-29 on current `origin/main` / workspace HEAD `d607ff36`. Running `./rust/target/debug/claw help --output-format json` produces parseable JSON, but the object only exposes top-level keys like `kind` and `message`; all command names, global flags, slash-command metadata, aliases, resume-safety, output-format support, auth/preflight notes, and descriptions are flattened into one human-oriented prose blob. That technically satisfies “valid JSON” while still forcing automation to scrape the same help text humans read, making `/issue`, `/help`, and resume-safety contracts opaque to claws. **Required fix shape:** (a) keep `message` as the compact human-rendered help summary, but add a documented structured schema with `schema` / `schema_version` fields; (b) expose first-class arrays/objects such as `commands[]`, `options[]`, and `slash_commands[]` with stable fields including `name`, `aliases`, `description`, `args`, `output_formats_supported`, `resume_safe`, `interactive_only`, and `creates_external_side_effects`; (c) include auth and creation preflight metadata where relevant, especially for GitHub/issue flows (`auth_preflight`, `creation_unavailable`, `gh_cli_authenticated`, `github_token_present`, or equivalent non-secret state); (d) make `/issue`, `/help`, aliases, and resume-dispatch safety machine-readable from the JSON payload instead of recoverable only by parsing prose markers; (e) add regression coverage proving `help --output-format json` is valid JSON and that `/issue`, `/help`, resume-safe vs interactive-only slash commands, aliases, descriptions, supported output formats, and side-effect/auth-preflight fields are present and internally consistent. **Why this matters:** help JSON is the discoverability surface automation uses before invoking commands. If it is just prose wrapped in JSON, claws cannot safely decide whether a command can run non-interactively, resume from a saved session, create external GitHub side effects, or requires auth/preflight without brittle text scraping. Source: gaebal-gajae dogfood follow-up from current main `d607ff36`; observed `./rust/target/debug/claw help --output-format json` returning valid JSON with only `{kind,message}` at the top level while the actionable command schema remained buried in `message`. -326. **`status --output-format json` underreports active workspace pane inventory when one tmux session has multiple panes/processes in the same project** — dogfooded 2026-04-29 on current `origin/main` / workspace HEAD `b90875fa` while responding to the claw-code dogfood nudge. The active OMX session `claw-code-issue-326-dogfood-pinpoint` was running in `/mnt/offloading/Workspace/claw-code` with two panes: `%9384` (`cmd=node`, active pane) and `%9385` (`cmd=node`, inactive sidecar pane). `tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index} #{pane_id} pid=#{pane_pid} cmd=#{pane_current_command} cwd=#{pane_current_path} active=#{pane_active}'` showed both panes in the same session/workspace, but `./rust/target/debug/claw status --output-format json` collapsed the workspace lifecycle to a single object: `session_lifecycle.kind = "running_process"`, `pane_id = "%9384"`, `pane_command = "node"`, with no `panes[]`, process count, sidecar/secondary-pane inventory, or ambiguity marker. A downstream claw reading only status JSON would believe there is exactly one live process for that workspace even though the control plane has multiple panes in the same task session. **Required fix shape:** (a) expose a structured active-session inventory in `status --output-format json`, including `panes[]` or `processes[]` with pane id, command, cwd, active flag, and session/window identity for all matching workspace panes; (b) keep the compact `session_lifecycle` summary, but add an explicit `pane_count` / `has_sidecar_panes` / `inventory_truncated` signal so summaries cannot masquerade as complete truth; (c) define how to classify primary vs sidecar/inactive panes without losing them, and make the chosen primary pane provenance visible; (d) add regression coverage for a tmux session with two panes in one workspace proving status JSON reports both panes or marks the inventory as partial. **Why this matters:** status JSON is the machine-readable lane truth surface. If it reports only the primary pane while hiding secondary panes, clawhip and other claws can miss sidecar workers, blocked helpers, stale subprocesses, or duplicated control-plane processes and make bad restart/cleanup/routing decisions from an undercounted session snapshot. Source: gaebal-gajae dogfood session `claw-code-issue-326-dogfood-pinpoint`; observed `claw status --output-format json` returning only `%9384` while `tmux list-panes` showed `%9384` and `%9385` in the same claw-code workspace. +326. **DONE — `status --output-format json` underreports active workspace pane inventory when one tmux session has multiple panes/processes in the same project** — dogfooded 2026-04-29 on current `origin/main` / workspace HEAD `b90875fa` while responding to the claw-code dogfood nudge. The active OMX session `claw-code-issue-326-dogfood-pinpoint` was running in `/mnt/offloading/Workspace/claw-code` with two panes: `%9384` (`cmd=node`, active pane) and `%9385` (`cmd=node`, inactive sidecar pane). `tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index} #{pane_id} pid=#{pane_pid} cmd=#{pane_current_command} cwd=#{pane_current_path} active=#{pane_active}'` showed both panes in the same session/workspace, but `./rust/target/debug/claw status --output-format json` collapsed the workspace lifecycle to a single object: `session_lifecycle.kind = "running_process"`, `pane_id = "%9384"`, `pane_command = "node"`, with no `panes[]`, process count, sidecar/secondary-pane inventory, or ambiguity marker. A downstream claw reading only status JSON would believe there is exactly one live process for that workspace even though the control plane has multiple panes in the same task session. **Required fix shape:** (a) expose a structured active-session inventory in `status --output-format json`, including `panes[]` or `processes[]` with pane id, command, cwd, active flag, and session/window identity for all matching workspace panes; (b) keep the compact `session_lifecycle` summary, but add an explicit `pane_count` / `has_sidecar_panes` / `inventory_truncated` signal so summaries cannot masquerade as complete truth; (c) define how to classify primary vs sidecar/inactive panes without losing them, and make the chosen primary pane provenance visible; (d) add regression coverage for a tmux session with two panes in one workspace proving status JSON reports both panes or marks the inventory as partial. **Why this matters:** status JSON is the machine-readable lane truth surface. If it reports only the primary pane while hiding secondary panes, clawhip and other claws can miss sidecar workers, blocked helpers, stale subprocesses, or duplicated control-plane processes and make bad restart/cleanup/routing decisions from an undercounted session snapshot. Source: gaebal-gajae dogfood session `claw-code-issue-326-dogfood-pinpoint`; observed `claw status --output-format json` returning only `%9384` while `tmux list-panes` showed `%9384` and `%9385` in the same claw-code workspace. 327. **DONE — `claw mcp help` omits `.claw.json` from its documented config sources even though `claw mcp` still loads MCP servers from `.claw.json`** — dogfooded 2026-04-29 on current `origin/main` / workspace HEAD `981aff7c` after rebuilding the actual debug binary with `cargo run --manifest-path rust/Cargo.toml --bin claw -- version --output-format json` so `./rust/target/debug/claw version --output-format json` reported embedded `git_sha` `981aff7c` matching the workspace. Running `./rust/target/debug/claw mcp --help` printed `Sources .claw/settings.json, .claw/settings.local.json`, and `./rust/target/debug/claw mcp help --output-format json` returned `"sources": [".claw/settings.json", ".claw/settings.local.json"]`. In the same rebuilt binary, a temp workspace containing only a project `.claw.json` with `{"mcpServers":{"demo":{"command":"/bin/echo","args":["hi"]}}}` made `./rust/target/debug/claw mcp --output-format json` report `configured_servers: 1` and `servers[0].name: "demo"`. The MCP lifecycle surface therefore tells users and claws that `.claw.json` is not a source while actively accepting it as one. This is distinct from #322's JSON warning corruption, #323/#326's status lifecycle contradictions, #324's stale-binary provenance gap, and #325's top-level help schema flattening: the pinpoint is a concrete MCP subcommand source-of-truth mismatch in both text and JSON help. **Required fix shape:** (a) derive the `mcp help` source list from the same `ConfigLoader::discover`/settings-source registry that `mcp list` actually uses instead of hard-coding a partial list; (b) include all supported MCP config sources in stable order, including legacy/project `.claw.json`, user `~/.claw/settings.json`, project `.claw/settings.json`, and local `.claw/settings.local.json` as applicable; (c) add source metadata to `mcp --output-format json` entries so each server can be attributed to the file/layer that provided it; (d) add a regression proving a server loaded from `.claw.json` is accompanied by help/JSON source metadata that names `.claw.json`, and that help stays in sync when config source discovery changes. **Why this matters:** MCP setup is already a high-friction lifecycle path; if the command that diagnoses MCP servers omits a still-supported source, operators can move or delete the wrong config file, and automation cannot tell whether `.claw.json` support is intentional compatibility or accidental legacy behavior. Source: gaebal-gajae dogfood in `/home/bellman/Workspace/claw-code` on 2026-04-29 using the rebuilt actual `./rust/target/debug/claw`; temp-workspace proof showed `.claw.json` loads one MCP server while `mcp help` documents only `.claw/settings*.json` sources. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 2c10f419..156df553 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -5808,6 +5808,8 @@ struct SessionLifecycleSummary { pane_path: Option, workspace_dirty: bool, abandoned: bool, + // #326: all panes matching this workspace, not just the first one + all_panes: Vec, } impl SessionLifecycleSummary { @@ -5833,6 +5835,14 @@ impl SessionLifecycleSummary { "pane_path": self.pane_path.as_ref().map(|path| path.display().to_string()), "workspace_dirty": self.workspace_dirty, "abandoned": self.abandoned, + // #326: include all workspace panes in the JSON output + "panes": self.all_panes.iter().map(|p| { + json!({ + "pane_id": p.pane_id, + "pane_command": p.current_command, + "pane_path": p.current_path.display().to_string(), + }) + }).collect::>(), }) } } @@ -5894,25 +5904,33 @@ fn classify_session_lifecycle_from_panes( panes: Vec, ) -> SessionLifecycleSummary { let workspace_dirty = git_worktree_is_dirty(workspace); - let mut idle_shell = None; + let mut idle_shell: Option = None; + let mut all_workspace_panes: Vec = Vec::new(); + let mut running_pane: Option = None; for pane in panes { if !pane_path_matches_workspace(&pane.current_path, workspace) { continue; } + all_workspace_panes.push(pane.clone()); if is_idle_shell_command(&pane.current_command) { idle_shell.get_or_insert(pane); - } else { - return SessionLifecycleSummary { - kind: SessionLifecycleKind::RunningProcess, - pane_id: Some(pane.pane_id), - pane_command: Some(pane.current_command), - pane_path: Some(pane.current_path), - workspace_dirty, - abandoned: false, - }; + } else if running_pane.is_none() { + running_pane = Some(pane); } } + if let Some(pane) = running_pane { + return SessionLifecycleSummary { + kind: SessionLifecycleKind::RunningProcess, + pane_id: Some(pane.pane_id), + pane_command: Some(pane.current_command), + pane_path: Some(pane.current_path), + workspace_dirty, + abandoned: false, + all_panes: all_workspace_panes, + }; + } + if let Some(pane) = idle_shell { SessionLifecycleSummary { kind: SessionLifecycleKind::IdleShell, @@ -5921,6 +5939,7 @@ fn classify_session_lifecycle_from_panes( pane_path: Some(pane.current_path), workspace_dirty, abandoned: workspace_dirty, + all_panes: all_workspace_panes, } } else { SessionLifecycleSummary { @@ -5930,6 +5949,7 @@ fn classify_session_lifecycle_from_panes( pane_path: None, workspace_dirty, abandoned: workspace_dirty, + all_panes: all_workspace_panes, } } } @@ -17455,6 +17475,7 @@ mod tests { pane_path: Some(PathBuf::from("/tmp/project")), workspace_dirty: true, abandoned: true, + all_panes: vec![], }, boot_preflight: test_boot_preflight(), sandbox_status: runtime::SandboxStatus::default(), @@ -17609,6 +17630,7 @@ mod tests { pane_path: None, workspace_dirty: false, abandoned: false, + all_panes: vec![], }, boot_preflight: test_boot_preflight(), sandbox_status: runtime::SandboxStatus::default(), @@ -17662,6 +17684,7 @@ mod tests { pane_path: None, workspace_dirty: false, abandoned: false, + all_panes: vec![], }, boot_preflight: test_boot_preflight(), sandbox_status: runtime::SandboxStatus::default(), @@ -17707,6 +17730,7 @@ mod tests { pane_path: Some(PathBuf::from("/tmp/project")), workspace_dirty: false, abandoned: false, + all_panes: vec![], }, boot_preflight: test_boot_preflight(), sandbox_status: runtime::SandboxStatus::default(),