Skip to content

fix: CI builds helpers from source; survive AX cycles; portable ax tests - #17

Merged
woladi merged 1 commit into
mainfrom
fix/ci-from-source
Aug 24, 2026
Merged

woladi merged 1 commit into
mainfrom
fix/ci-from-source

Conversation

@woladi

@woladi woladi commented Aug 24, 2026

Copy link
Copy Markdown
Owner

main is currently red — the Release run after #16 failed — and this is the fix.

CI was testing the previous release, not the branch

postinstall downloads prebuilt helpers for the package's current version. Once that version is published, CI on a branch that changes Swift finds those assets and runs the whole suite against the previous release. Native changes were never tested.

It surfaced when two tests covering behaviour added in #16 failed on CI while passing locally: the runner was executing a binary that predated them. CI now sets MACOS_VISION_SKIP_DOWNLOAD=1 and asserts each helper exists and is newer than its source.

The AX tree is a graph, and the walk assumed it was a tree

Safari lists the application element as its own child, and answers kAXWindows with it:

axApp role: AXApplication
kAXWindows → count 1, same element as axApp? [true]
children   → ["AXApplication", "AXMenuBar"], sameAsApp: [true, false]

Without cycle protection the walk descends into the application repeatedly and returns menu bars at the depth limit instead of the window — the cause of an occasional two-node result seen while wiring this into the MCP server. Visited elements are now tracked by CFHash/CFEqual, and windows are taken from the union of kAXWindows and children, filtered to real windows and never the app element.

The tests asserted a precondition instead of checking it

They targeted Finder. CGWindowList still lists windows on a locked Mac while AX exposes none, and a CI runner has neither — so the fixed target passed locally and failed on CI, where it had only ever passed by exercising the very fallback #16 removed. They now probe until they find an app AX actually answers for, and skip when none does.

Verification, and what is not verified

105/105 with the screen locked, which is the headless case: the axTree tests skip cleanly and everything else passes. CI on this PR exercises that path for real, compiling from source.

Not verified by me: the windowed path with this new union enumeration — the machine locked itself and stays locked behind a password. The union is a superset of the children-only enumeration that was returning 224 nodes correctly before, so the risk is low, but it deserves one run on an unlocked Mac before release.

🤖 Generated with Claude Code

…urces

The accessibility tree is a graph. Safari lists the application element as
its own child and answers kAXWindows with it, so the walk descended into
the application over and over and returned menu bars at the depth limit
instead of the window — the cause of an occasional two-node result.
Visited elements are now tracked by CFHash/CFEqual.

Windows come from the union of kAXWindows and the application element's
children, filtered to real windows and never the app element, since apps
differ in which they populate. The union is a superset of the
children-only path that was working before.

The tests stopped assuming a fixed app has a window. They now probe until
the accessibility API actually answers and skip when nothing does:
CGWindowList still lists windows on a locked Mac while AX exposes none,
and a CI runner has neither — which is why a hard-coded Finder target
passed locally and failed on CI, where it had only ever passed by
exercising the fallback these tests exist to forbid.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@woladi
woladi merged commit 77422c3 into main Aug 24, 2026
1 check passed
@woladi
woladi deleted the fix/ci-from-source branch August 24, 2026 17:41
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