Skip to content

fix(cua-driver): persist set_config to disk on Windows + Linux (config parity) - #2034

Merged
f-trycua merged 1 commit into
mainfrom
fix/persist-config-win-linux
Jun 26, 2026
Merged

fix(cua-driver): persist set_config to disk on Windows + Linux (config parity)#2034
f-trycua merged 1 commit into
mainfrom
fix/persist-config-win-linux

Conversation

@f-trycua

@f-trycua f-trycua commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Persist set_config to disk on Windows and Linux so config survives across stateless cua-driver call invocations — bringing them to parity with macOS, which already does this. Addresses #2011 (Windows config disagreement) and fixes the footgun where the desktop-scope flow (#2019) couldn't hold capture_scope across CLI calls.

The bug

Each cua-driver call is a fresh process. On macOS, ToolState::default() loads ~/.cua-driver/config.json at startup and set_config writes keys back, so config persists across calls. On Windows/Linux, ToolState::new() built DriverConfig::default() with no disk load, and set_config mutated only the in-memory config for capture_mode / max_image_dimension (only experimental_pip* ever touched disk). So a separate cua-driver call get_config reverted to defaults (som). This is exactly why set_config capture_scope=desktop then a separate click didn't take effect.

Changes (3 files)

  • crates/pip-preview/src/lib.rs (the already-shared config helper):
    • default_config_path() now falls back to %USERPROFILE% when HOME is unset. Windows runs with HOME empty, so without this the config file can't even be resolved there — meaning the existing pip persistence was latently broken on Windows too.
    • Added read_config_value(key) so per-platform loaders can read arbitrary keys.
  • crates/platform-windows/.../tools/impl_.rs & crates/platform-linux/.../tools/impl_.rs:
    • Added load_driver_config() (mirrors macOS), wired into ToolState::new() to load capture_mode + max_image_dimension from disk at startup.
    • set_config now persists both fields via pip_preview::write_config_key, in both input shapes ({key,value} and legacy per-field). Persist failures warn! and continue, matching macOS.

platform-macos is untouched (already correct).

Verified live (two separate call processes)

  • Linux (cua-gnome-test): fresh get_configsom; set_config capture_mode=vision in one process; separate get_configvision. On-disk /root/.cua-driver/config.json = {"capture_mode":"ax","max_image_dimension":800}.
  • Windows (fbonacci-windows-vm, with HOME empty): same round-trip persists; on-disk C:\Windows\system32\config\systemprofile\.cua-driver\config.json confirms the %USERPROFILE% fallback works.

(Tested with capture_mode because capture_scope only exists on the #2019 branch — it exercises the identical persistence path.)

Relationship to other PRs

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01KMXCW4M5uK1HRGjjH4wueZ

Summary by CodeRabbit

  • New Features

    • Driver settings now persist between app launches on both Linux and Windows.
    • Configuration loading now works from either the standard home directory or the Windows user profile path.
  • Bug Fixes

    • Saved settings are now restored even when the app starts without prior in-memory state.
    • Missing or malformed configuration data now falls back safely to default values.
    • Configuration updates continue to apply even if saving to disk fails.

@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Jun 26, 2026 3:38am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 33fd0ff7-d80a-4ae9-a210-15a43f14f6e9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds config JSON helpers, loads capture_mode and max_image_dimension into Linux and Windows driver startup state, and persists those settings back to ~/.cua-driver/config.json from set_config on both platforms.

Changes

Driver config persistence

Layer / File(s) Summary
Config file helpers
libs/cua-driver/rust/crates/pip-preview/src/lib.rs
default_config_path() now falls back to USERPROFILE, and read_config_value() returns a JSON key from the config file or None on missing env, unreadable file, malformed JSON, or absent key.
Startup loading
libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs, libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs
load_driver_config() overrides capture_mode and max_image_dimension from ~/.cua-driver/config.json, and ToolState::new() on both platforms uses it for initial config state.
Persisted set_config updates
libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs, libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs
Both {key, value} and legacy field-shaped set_config inputs now write capture_mode and max_image_dimension back to config.json, warning on persistence errors while keeping the in-memory update.

Sequence Diagram(s)

sequenceDiagram
  participant ToolStateNew
  participant SetConfigInvoke
  participant ReadConfigValue
  participant WriteConfigKey
  participant ConfigFile

  ToolStateNew->>ReadConfigValue: load capture_mode and max_image_dimension
  ReadConfigValue->>ConfigFile: read JSON key
  ConfigFile-->>ReadConfigValue: stored config data
  ReadConfigValue-->>ToolStateNew: DriverConfig overrides

  SetConfigInvoke->>WriteConfigKey: persist capture_mode or max_image_dimension
  WriteConfigKey->>ConfigFile: write updated key
  ConfigFile-->>WriteConfigKey: write result
  WriteConfigKey-->>SetConfigInvoke: warn on failure, continue with in-memory cfg
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • trycua/cua#1518: Both PRs implement the same ~/.cua-driver/config.json persistence for capture_mode/max_image_dimension by adding load_driver_config()-style startup loading and set_config writing (macOS vs Linux/Windows), so the changes are directly related.
  • trycua/cua#1730: Both PRs modify the shared pip-preview config-file handling (default_config_path/config JSON reads/writes) and extend platform-{linux,windows}/src/tools/impl_.rs config persistence into ~/.cua-driver/config.json, so the changes overlap at the same code points.
  • trycua/cua#1928: The Linux {key,value} set_config dispatch this PR extends to persist capture_mode and max_image_dimension builds on the same Linux config-update path.

Poem

A rabbit hops through JSON bright,
and tucks new values in at night.
HOME or USERPROFILE, I find the way,
then write my config back to stay.
Hop hop—my settings never stray.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: persisting set_config to disk on Windows and Linux for config parity.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/persist-config-win-linux

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs (1)

29-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

load_driver_config() is duplicated verbatim in platform-windows.

This exact helper now exists in both platform-linux and platform-windows tools/impl_.rs. Since both only depend on pip_preview::read_config_value and DriverConfig, consider hoisting it (or the read logic) into the pip-preview crate to keep the two platforms from drifting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs` around lines
29 - 44, The load_driver_config helper is duplicated across the Linux and
Windows platform tool implementations, so the config-loading logic can drift
over time. Move the shared read-and-apply behavior behind a single reusable
helper in pip_preview, or otherwise centralize it there, and have both
platform-linux and platform-windows call that shared entry point while still
populating DriverConfig from pip_preview::read_config_value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/cua-driver/rust/crates/pip-preview/src/lib.rs`:
- Around line 23-26: The default_config_path function is treating empty HOME or
USERPROFILE values as valid, which can produce a relative
.cua-driver/config.json path instead of None. Update default_config_path to
check the environment variables for both presence and non-empty content before
building the PathBuf, and return None when either HOME or USERPROFILE is unset
or empty so the function’s contract is preserved.

In `@libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs`:
- Around line 3429-3445: Validate the config inputs in the set_config path
before writing them out: in the branches handling capture_mode and
max_image_dimension, reject unsupported or overflowing values instead of
persisting them unchanged. For capture_mode, only accept the known string values
already recognized by the config loader and return a ToolResult::error for
anything else so unknown modes cannot be saved. For max_image_dimension, check
that the incoming u64 fits in u32 before assigning to cfg.max_image_dimension
and before calling pip_preview::write_config_key, and persist the same u32 value
that is stored in memory.

---

Nitpick comments:
In `@libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs`:
- Around line 29-44: The load_driver_config helper is duplicated across the
Linux and Windows platform tool implementations, so the config-loading logic can
drift over time. Move the shared read-and-apply behavior behind a single
reusable helper in pip_preview, or otherwise centralize it there, and have both
platform-linux and platform-windows call that shared entry point while still
populating DriverConfig from pip_preview::read_config_value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c39ad09e-3435-481c-abed-c1daee571b5d

📥 Commits

Reviewing files that changed from the base of the PR and between 46efc5f and 3f0eb2c.

📒 Files selected for processing (3)
  • libs/cua-driver/rust/crates/pip-preview/src/lib.rs
  • libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs
  • libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs

Comment on lines 23 to 26
pub fn default_config_path() -> Option<std::path::PathBuf> {
std::env::var("HOME")
.ok()
std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(|h| std::path::PathBuf::from(h).join(".cua-driver").join("config.json"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Treat empty HOME / USERPROFILE as unset.

Line 24 and Line 25 use var_os directly, so HOME="" or USERPROFILE="" becomes .cua-driver/config.json relative to the current working directory instead of returning None. That breaks the documented contract here and can persist config to the wrong location.

Suggested fix
 pub fn default_config_path() -> Option<std::path::PathBuf> {
     std::env::var_os("HOME")
         .or_else(|| std::env::var_os("USERPROFILE"))
+        .filter(|h| !h.is_empty())
         .map(|h| std::path::PathBuf::from(h).join(".cua-driver").join("config.json"))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn default_config_path() -> Option<std::path::PathBuf> {
std::env::var("HOME")
.ok()
std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(|h| std::path::PathBuf::from(h).join(".cua-driver").join("config.json"))
pub fn default_config_path() -> Option<std::path::PathBuf> {
std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.filter(|h| !h.is_empty())
.map(|h| std::path::PathBuf::from(h).join(".cua-driver").join("config.json"))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/rust/crates/pip-preview/src/lib.rs` around lines 23 - 26, The
default_config_path function is treating empty HOME or USERPROFILE values as
valid, which can produce a relative .cua-driver/config.json path instead of
None. Update default_config_path to check the environment variables for both
presence and non-empty content before building the PathBuf, and return None when
either HOME or USERPROFILE is unset or empty so the function’s contract is
preserved.

Comment on lines +3429 to +3445
Some(s) => {
cfg.capture_mode = s.to_owned();
if let Err(e) = pip_preview::write_config_key("capture_mode", Value::String(s.to_owned())) {
tracing::warn!("set_config: failed to persist capture_mode: {e}");
}
parts.push(format!("capture_mode={s}"));
}
None => return ToolResult::error(format!("`capture_mode` must be a string, got {val}.")),
},
"max_image_dimension" => match val.as_u64() {
Some(n) => { cfg.max_image_dimension = n as u32; parts.push(format!("max_image_dimension={n}")); }
Some(n) => {
cfg.max_image_dimension = n as u32;
if let Err(e) = pip_preview::write_config_key("max_image_dimension", Value::from(n)) {
tracing::warn!("set_config: failed to persist max_image_dimension: {e}");
}
parts.push(format!("max_image_dimension={n}"));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# capture_mode consumers / validation in cua-driver
rg -nP --type=rust -C3 'capture_mode' libs/cua-driver/rust/crates | rg -nP -C3 'match|enum|som|vision|ax|parse|validate'

Repository: trycua/cua

Length of output: 50367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE='libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs'

# Map the relevant symbols first, then inspect the smallest useful ranges.
ast-grep outline "$FILE" --view expanded | sed -n '1,220p'

printf '\n--- load_driver_config / set_config ranges ---\n'
rg -n -C 4 'fn load_driver_config|capture_mode|max_image_dimension|set_config|DriverConfig' "$FILE"

Repository: trycua/cua

Length of output: 21516


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE='libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs'

# Show the relevant code around config load and set_config.
sed -n '150,220p' "$FILE"
printf '\n---\n'
sed -n '5000,5065p' "$FILE"

Repository: trycua/cua

Length of output: 3675


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE='libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs'

python3 - <<'PY'
from pathlib import Path
file = Path("libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs")
text = file.read_text()
for needle in [
    "pub fn load_driver_config()",
    "\"capture_mode\" => match val.as_str()",
    "\"max_image_dimension\" => match val.as_u64()",
]:
    idx = text.find(needle)
    print(f"== {needle} == {idx}")
    if idx != -1:
        start = max(0, text.rfind("\n", 0, idx-400))
        end = text.find("\n", idx+900)
        print(text[start:end])
        print("\n---")
PY

Repository: trycua/cua

Length of output: 4447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE='libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs'

echo '--- load_driver_config ---'
sed -n '169,190p' "$FILE"

echo
echo '--- set_config capture_mode / max_image_dimension ---'
sed -n '5030,5058p' "$FILE"

echo
echo '--- capture_mode consumers ---'
rg -n -C 2 'do_tree|do_shot|capture_mode !=|capture_mode ==' "$FILE"

Repository: trycua/cua

Length of output: 2424


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE='libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs'

# Narrowly inspect the exact file under review.
rg -n -C 3 'pub fn load_driver_config|capture_mode|max_image_dimension|set_config|do_tree|do_shot' "$FILE"

Repository: trycua/cua

Length of output: 10954


Validate config values before persisting.

  • capture_mode is accepted as any string on the {key, value} path and reloaded unchanged. Unknown values don’t fail startup, but they are persisted and later behave like som because only "vision" and "ax" are special-cased.
  • max_image_dimension truncates u64 -> u32 in memory, while the full u64 is written to disk. Values above u32::MAX come back as the default on reload; reject overflow or persist the same u32 value.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/rust/crates/platform-linux/src/tools/impl_.rs` around lines
3429 - 3445, Validate the config inputs in the set_config path before writing
them out: in the branches handling capture_mode and max_image_dimension, reject
unsupported or overflowing values instead of persisting them unchanged. For
capture_mode, only accept the known string values already recognized by the
config loader and return a ToolResult::error for anything else so unknown modes
cannot be saved. For max_image_dimension, check that the incoming u64 fits in
u32 before assigning to cfg.max_image_dimension and before calling
pip_preview::write_config_key, and persist the same u32 value that is stored in
memory.

`set_config` (e.g. capture_mode) only mutated the in-memory `DriverConfig`
on Windows and Linux, so a value set in one `cua-driver call` process was
lost in the next — each stateless one-shot reverted to the default. macOS
already loads/saves `~/.cua-driver/config.json` at startup; this brings the
other two platforms to parity.

- pip-preview: `default_config_path` now falls back to `%USERPROFILE%` when
  `HOME` is unset (Windows), and add `read_config_value(key)` so the
  per-platform loaders can rehydrate `DriverConfig` from the shared file.
- platform-windows / platform-linux: add `load_driver_config()` (called from
  `ToolState::new`) to load capture_mode + max_image_dimension on startup,
  and persist both fields via `pip_preview::write_config_key` in `set_config`
  (both the {key,value} and legacy per-field shapes).

Config now survives across separate `cua-driver call` invocations for all
persisted fields, fixing the desktop-scope footgun.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMXCW4M5uK1HRGjjH4wueZ
Co-authored-by: outdog-hwh <168953287+outdog-hwh@users.noreply.github.com>
@f-trycua
f-trycua force-pushed the fix/persist-config-win-linux branch from 3f0eb2c to b58e7d4 Compare June 26, 2026 03:38
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Linux visual regression artifacts

Matrix jobs now run independently. Download visual artifacts from this workflow run.
Each background-GUI job uploads a .gif of the interaction plus two annotated PNGs (<app>.png raw, <app>-atspi.png with AT-SPI element boxes); the cua-driver-linux-som-overlays artifact adds <app>-som.png cua Set-of-Marks overlays:

  • cua-driver-linux-cursor-click-gif
  • cua-driver-linux-background-terminal-gif
  • cua-driver-linux-parallel-drag-xserver
  • cua-driver-linux-background-gui-chromium
  • cua-driver-linux-background-gui-tk
  • cua-driver-linux-background-gui-gtk3-gedit
  • cua-driver-linux-background-gui-gtk3-mousepad
  • cua-driver-linux-background-gui-gtk3-scite
  • cua-driver-linux-background-gui-gtk4-characters
  • cua-driver-linux-background-gui-qt5-manuskript
  • cua-driver-linux-background-gui-qt5-klog
  • cua-driver-linux-background-gui-qt5-openambit
  • cua-driver-linux-background-gui-qt6-kate
  • cua-driver-linux-background-gui-qt6-kcalc
  • cua-driver-linux-background-gui-qt6-okular
  • cua-driver-linux-background-gui-qt6-qownnotes
  • cua-driver-linux-background-gui-electron-zettlr
  • cua-driver-linux-background-gui-electron-joplin
  • cua-driver-linux-background-gui-electron-logseq
  • cua-driver-linux-som-overlays

Open workflow run and download artifacts

@f-trycua
f-trycua merged commit e08574c into main Jun 26, 2026
71 checks passed
@f-trycua
f-trycua deleted the fix/persist-config-win-linux branch June 26, 2026 03:46
f-trycua added a commit that referenced this pull request Jun 26, 2026
…t coverage (Phases 3-6) (#2043)

* refactor(cua-driver): test-suite taxonomy, protocol split+dedup, transport test (Phases 3-6)

Phase 3 — rename tests into a 4-family taxonomy (git mv, blame preserved):
  protocol_ / transport_ / harness_ / modality_ / guard_.
  harness_web_windows→harness_web, harness_lo_vcl→harness_libreoffice,
  harness_bg_modality→modality_background, harness_desktop_scope→modality_desktop_scope,
  focus_check→modality_focus, e2e_windows_bg_input→modality_input_e2e,
  ux_guard→guard_ux, element_token→protocol_element_token.

Phase 5 — split mcp_protocol_test (3,412 lines / 65 tests) into 5 protocol_*_test
  files (handshake/tools_call/schema/media/session) on a new testkit RawDriver
  (raw send/recv, no auto-init). Deduped 24 macOS↔Windows mirror pairs into single
  cfg!-branching tests; 13 mac-only + 4 win-only kept. All 40 macOS protocol tests
  pass post-split.

Phase 4 — add transport_config_persistence_test: set_config persists to disk
  across stateless CLI invocations (#2034) vs visible within an MCP session — the
  one behavior only observable across both transports.

Phase 6 — rewrite TEST_SUITE.md / TEST_HARNESS_STRUCTURE.md to the new names +
  a transport × modality × platform coverage matrix.

Deferred (noted): merging modality_input_e2e ↔ modality_background, and
extracting the focus-steal sentinel into a shared testkit assert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(cua-driver): fix stale Windows protocol assertions surfaced by running them

The old mcp_protocol Windows mirror asserted serverInfo.name="cua-driver-rs"
and a roster with type_text_chars+screenshot — both stale (the Windows mirror
had never actually run). The live Windows server returns name="cua-driver" and
registers neither extra tool. Drop the bogus platform branches; the macOS values
are correct for both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(cua-driver): fix 3 more stale Windows protocol branches found by running

Running the split suite on Windows surfaced three never-run stale assertions:
- schema: the Windows server has no type_text_chars tool (hidden on both
  platforms), so merge the two type_text_chars schema tests into one.
- media set_config_screenshot_resize: skip the resize assertion when the
  screenshot is unavailable (Session 0 / no display) on BOTH platforms, not
  just macOS.
- session multi_cursor: Windows get_agent_cursor_state returns ALL cursors
  (macOS scopes to one); verify each cursor's state within the full list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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