verifier-settings — drive the settings window
This skill verifies changes to the desktop app's settings window (settings sections, plugins gallery, snip panel, NSSplitView layout, window sizing). The app is an LSUIElement accessory app — osascript click / AXPress / keystroke do not route to its windows (patterns/2026-06-23). The only reliable automation path is the buddy launcher debug CLI, which talks to the running app over its Unix socket and drives the settings window in-process.
The harness is driver.sh in this directory. Run that; don't improvise GUI automation.
Paths below are relative to repo root (claude-code-buddy/). The driver lives at .claude/skills/verifier-settings/driver.sh.
Prerequisites
- macOS (this is a native Swift/AppKit app — it cannot run on Linux).
- Xcode + Swift toolchain, SwiftLint (
brew install swiftlint). - The app must be bundled so the in-bundle
buddyCLI (with thedebug open-settings/select-section/select-plugin/get-statesubcommands) exists. The Homebrew-installed/opt/homebrew/bin/buddyis an older build without these.
Build + launch (one-time per session)
SKIP_FETCH_PLUGINS=1 make -C apps/desktop bundle
pkill -f ClaudeCodeBuddy; sleep 1
open apps/desktop/ClaudeCodeBuddy.app
sleep 3
SKIP_FETCH_PLUGINS=1 skips the plugin fetch (faster; fine when bundled plugins already exist).
Drive the window (agent path — use driver.sh)
The in-bundle CLI is at apps/desktop/ClaudeCodeBuddy.app/Contents/MacOS/buddy. The driver wraps it.
Full sweep (build → launch → screenshot every panel → cleanup):
.claude/skills/verifier-settings/driver.sh all
Or step-by-step:
BUDDY=apps/desktop/ClaudeCodeBuddy.app/Contents/MacOS/buddy
# Open the settings window (optionally preselect a section). This is the unblocker:
$BUDDY launcher debug open-settings general
# Switch main section: general | about | hotkey | skins | plugins
$BUDDY launcher debug select-section about
# In the plugins section, select a plugin panel (e.g. snip):
$BUDDY launcher debug select-section plugins
$BUDDY launcher debug select-plugin snip
# Dump window geometry + selection (JSON — for frame predicates):
$BUDDY launcher debug get-state
# Screenshot:
screencapture -x /tmp/verifier-settings-evidence/<name>.png
# Drive + capture one panel via the driver:
.claude/skills/verifier-settings/driver.sh drive snip
# Cleanup:
pkill -f ClaudeCodeBuddy
Screenshots + get-state JSON land in /tmp/verifier-settings-evidence/ (override with VERIFIER_EVIDENCE_DIR=...).
What get-state returns (frame predicates)
{
"data": {
"window_open": true,
"window": { "x":.., "y":.., "width":.., "height":.., "isKeyWindow":true, "title":"设置" },
"selectedSection": "plugins", // general|about|hotkey|skins|plugins
"sidebarWidth": 200, // expect 200 (SettingsTheme.sidebarWidth)
"detailVC": "PluginGalleryViewController",
"detailAX": "settings.detail",
"pluginListWidth": 240, // plugins section only; expect 240
"contentColumnWidth": 359, // plugins section only; expect >0 and ≤780
"selectedPlugin": "snip" // plugins section only
}
}
Assert against: sidebarWidth == 200, pluginListWidth == 240, contentColumnWidth > 0 (and ≤ 780), window.width >= 800 (minSize), isKeyWindow == true.
Run (human path)
open apps/desktop/ClaudeCodeBuddy.app # cat appears in menu bar
# Click the menu-bar cat → 设置 (osascript can't do this click reliably)
Useless for headless/automated verification — that's why this skill exists.
Gotchas
- LSUIElement = no osascript routing.
osascript -e 'tell process "ClaudeCodeBuddy" to AXPress status item'returns success but the settings window does not open.osascriptkeystroke/click likewise no-ops. Only thebuddy launcher debugCLI (in-process socket) opens/switches the window. AX read (window frame) is reliable and can cross-checkget-state. - Use the in-bundle
buddy, not the Homebrew one./opt/homebrew/bin/buddyis an older release without thedebug open-settingsfamily. Always invokeapps/desktop/ClaudeCodeBuddy.app/Contents/MacOS/buddyafter a freshmake bundle. - Window sizing: NSSplitViewController shrinks items to content fittingSize on section switch. This was a real defect: the detail
splitViewItemgot sized to its content'sfittingWidth(the plugins gallery =pluginSidebar 240 + detailContainer 0 = 240), which (a) squeezed the gallery's right column /ContentColumnViewto 0 width → blank right panel, and (b) collapsed the window below its own minSize. Fixed two ways inSettingsSplitViewController:detailItem.minimumThickness = 600(init) floors the detail item so the right column renders, and aview.heightAnchor ≥ 540constraint (viewDidLoad) floors window height. After the fix the window holds at ~808×572 andcontentColumnWidthreads ~359. If the plugins right panel goes blank again or the window collapses,detailItem.minimumThicknessregressed. contentColumnWidthis a usable predicate (reads ~359 in the plugins section after the above fix). If it reads 0, the right panel is squeezed blank — see the previous bullet.osascriptwindow enumeration is unreliable for non-key windows. After the settings window loses key focus (~1s after CLI open),osascript … count windowsdrops it (returns only the cat-scene window) even though it's visibly on screen. AX read of a key window is reliable; enumeration of a backgrounded one is not. Use in-processget-statefor the frame, notosascript.- Window loses key focus during
screencapture.get-statemay showisKeyWindow: falseright after a screenshot — re-runopen-settingsif you need it key. make build≠make bundle.make buildcompiles to.build/debug/;open ClaudeCodeBuddy.appruns the bundled app. Alwaysmake bundleso the running app + in-bundle CLI reflect your changes.
Troubleshooting
| Symptom | Fix |
|---|---|
buddy launcher debug open-settings → Buddy app is not running |
open apps/desktop/ClaudeCodeBuddy.app; sleep 3 (wait for socket) |
open-settings reports ok but window invisible / sub-minSize / plugins right panel blank |
detailItem.minimumThickness (=600) or the height≥540 constraint regressed — re-check SettingsSplitViewController. Blank right panel = contentColumnWidth reads 0. |
select-plugin snip → plugin not found or gallery not loaded |
Gallery starts .loading and refreshes async; the handler awaits refresh() before selecting. Retry once; if persistent, check PluginPanelRegistry registers "snip" (PluginGalleryViewController.init). |
| App vanishes mid-drive | A prior viewDidLayout/setFrame "fight the shrink" fix recursed and crashed the app. The current fix (fittingSize floor) does not recurse — if a crash returns, that approach was reintroduced. |
make bundle fails on fetch |
SKIP_FETCH_PLUGINS=1 make -C apps/desktop bundle |
Scope notes
This skill drives the settings window only (sections + plugins + snip). For cat-scene / session-state verification use buddy inspect / buddy click (see apps/desktop/CLAUDE.md QA section). For full manual E2E there is a separate buddy-e2e-test skill.