| name | verifier-settings |
| description | Build, launch, and DRIVE the Claude Code Buddy macOS settings window — open it, switch panels (general/about/hotkey/plugins/snip), and capture screenshots + frame geometry as verification evidence. Use when verifying changes to the settings/plugins/snip UI, the NSSplitView layout, or any settings-window-sizing change. Bypasses the LSUIElement osascript-click-doesn't-route limitation via the `buddy launcher debug` CLI. |
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
buddy CLI (with the debug open-settings / select-section / select-plugin / get-state subcommands) exists. The Homebrew-installed /opt/homebrew/bin/buddy is 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
$BUDDY launcher debug open-settings general
$BUDDY launcher debug select-section about
$BUDDY launcher debug select-section plugins
$BUDDY launcher debug select-plugin snip
$BUDDY launcher debug get-state
screencapture -x /tmp/verifier-settings-evidence/<name>.png
.claude/skills/verifier-settings/driver.sh drive snip
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",
"sidebarWidth": 200,
"detailVC": "PluginGalleryViewController",
"detailAX": "settings.detail",
"pluginListWidth": 240,
"contentColumnWidth": 359,
"selectedPlugin": "snip"
}
}
Assert against: sidebarWidth == 200, pluginListWidth == 240, contentColumnWidth > 0 (and ≤ 780), window.width >= 800 (minSize), isKeyWindow == true.
Run (human path)
open apps/desktop/ClaudeCodeBuddy.app
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. osascript keystroke/click likewise no-ops. Only the buddy launcher debug CLI (in-process socket) opens/switches the window. AX read (window frame) is reliable and can cross-check get-state.
- Use the in-bundle
buddy, not the Homebrew one. /opt/homebrew/bin/buddy is an older release without the debug open-settings family. Always invoke apps/desktop/ClaudeCodeBuddy.app/Contents/MacOS/buddy after a fresh make bundle.
- Window sizing: NSSplitViewController shrinks items to content fittingSize on section switch. This was a real defect: the detail
splitViewItem got sized to its content's fittingWidth (the plugins gallery = pluginSidebar 240 + detailContainer 0 = 240), which (a) squeezed the gallery's right column / ContentColumnView to 0 width → blank right panel, and (b) collapsed the window below its own minSize. Fixed two ways in SettingsSplitViewController: detailItem.minimumThickness = 600 (init) floors the detail item so the right column renders, and a view.heightAnchor ≥ 540 constraint (viewDidLoad) floors window height. After the fix the window holds at ~808×572 and contentColumnWidth reads ~359. If the plugins right panel goes blank again or the window collapses, detailItem.minimumThickness regressed.
contentColumnWidth is 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.
osascript window enumeration is unreliable for non-key windows. After the settings window loses key focus (~1s after CLI open), osascript … count windows drops 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-process get-state for the frame, not osascript.
- Window loses key focus during
screencapture. get-state may show isKeyWindow: false right after a screenshot — re-run open-settings if you need it key.
make build ≠ make bundle. make build compiles to .build/debug/; open ClaudeCodeBuddy.app runs the bundled app. Always make bundle so 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.