Skip to content

Bug fixes: spacing relaunch, hotkeys, XPC on ad hoc builds, permission wait, data race, allowlist - #35

Merged
holzcloud merged 25 commits into
mainfrom
claude/ice-fork-development-hzdl1d
Oct 2, 2026
Merged

holzcloud merged 25 commits into
mainfrom
claude/ice-fork-development-hzdl1d

Conversation

@holzcloud

@holzcloud holzcloud commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Phase 2, the bug fixes, is complete: this pull request fixes BUG-01 to BUG-08 (spacing relaunch, hotkeys, XPC on ad hoc builds, permission wait, data race, allowlist) and removes the app's former name from the repository. It stays a draft until it is marked ready.

  • BUG-01 Applying menu bar item spacing relaunches every app with a menu bar item: a skipped process (Control Center or holzBar itself) no longer ends the loop. The logic lives in the new pure holzBar/Core folder (HolzBarCore, SpacingRelaunch suite).
  • BUG-02 An app gets 10 seconds to quit and is never force terminated; one that is still running is left alone and named in the alert. The wait observes isTerminated instead of polling and always returns, at the event, the timeout or cancellation.
  • BUG-03 On macOS 27 the owners of the items come from the Accessibility read, and MenuBarAgent is never quit.
  • BUG-04 The event source cache in MenuBarItemManager.getEventSource(with:) is read and filled only inside one OSAllocatedUnfairLock (withLockUnchecked, as CGEventSource is not Sendable), so concurrent item moves and clicks can no longer corrupt it.
  • BUG-05 On macOS 15 and later the hotkey recorder refuses a combination whose only modifiers are Option, or Option and Shift, and explains that Command or Control is needed (Modifiers suite).
  • BUG-06 The macOS 26 menu bar item service accepts holzBar's own ad hoc builds, which have no team identifier, and still rejects every other process: it requires holzBar's signing identifier and one of the code directory hashes of the app it is embedded in (same team plus identifier on a team-signed build), and does not listen at all when it cannot build that requirement. Shared/CodeSigning/CodeSignature.swift reads the hashes with Security only, so the app still launches on macOS 14.0 (CodeSignature suite).
  • BUG-07 Permission.waitForPermission() returns a Bool and any number of waits end, so the permissions window comes to the front once when a permission is granted and does not reopen by itself.
  • BUG-08 The macOS 27 system item allowlist is 0 through 127, the range MenuBarAgent was measured to accept (one at a time and all at once), instead of 0 through 63. It lives in the new Core file SystemItems27.swift (SystemItems27 suite), the assertion builds its configuration from it, and its comment says what the code does: macOS 27 support: remaining gaps on top of #995 jordanbaird/Ice#1001 keeps 0 to 63, which lies inside it.
  • Cleanup The app's former name is gone from the repository. The cask points only at holzBar (no rename mapping), the app imports settings only from Ice and accepts only holzbar://, an unknown stored icon decodes as the default, past release notes say holzBar, and a former-name job in build.yml keeps the name out.

Verified by: the CI build (BUILD SUCCEEDED, no warning in the changed files), swift test (140 tests in 26 suites, including the new SpacingRelaunch, Modifiers, CodeSignature and SystemItems27 suites of the HolzBarCore, SharedCodeSigning and HolzBarMacOS27Core targets), and strict SwiftLint (0 violations).

Still to check by hand on a Mac:

  • macOS 26.7.1: with several apps that have menu bar items running, change "Menu bar item spacing" and press Apply. Every app with a menu bar item quits and reopens, including when Control Center or holzBar would have come first. Repeat once: the same set relaunches.
  • macOS 26.7.1: run an app that asks before quitting (leave the question unanswered) and apply spacing. The app is not killed; after about 10 s holzBar shows "did not quit within 10 seconds and were not restarted" naming it; the other apps relaunch.
  • macOS 27: apply spacing. No alert names MenuBarAgent, and the apps with menu bar items relaunch. Note whether the spacing between items changes; if it does not, MenuBarAgent reads the setting itself and a follow-up is needed.
  • macOS 26.7.1, Settings, Hotkeys: Option-H shows "macOS does not allow this hotkey" saying Command or Control is needed, and after OK the recorder still records; Option-Shift-H shows the same alert; Command-Option-H records and fires from another app. Hotkeys set before the update still work.
  • Permissions window with Screen Recording reset (tccutil reset ScreenCapture com.holzcloud.holzBar): "Grant Permission", then "Reset and Grant Again", then grant in System Settings: the window comes to the front once and shows it as granted. Reset again, "Grant Permission", do not grant, click Continue: the window does not reopen by itself later.
  • macOS 26.7.1 with an ad hoc build (CI or Scripts/install.sh): with log stream --level info --predicate 'process == "MenuBarItemService" OR subsystem BEGINSWITH "com.holzcloud.holzBar"' running, launch holzBar and open Settings, Menu Bar Layout. The service logs "Listener requires the app's exact code (N code directory hashes)" with N ≥ 1, the layout shows the menu bar items instead of "Loading menu bar items…", and no "looking up source processes in the app instead" line appears.
  • macOS 26.7.1, Settings, Menu Bar Layout: drag several items between sections in quick succession, and click hidden items in the holzBar Shelf while a move is still running. Items move and open as before; holzBar does not crash.
  • macOS 27: hide a few apps' items with holzBar. Battery, clock, Wi-Fi and Control Centre stay on the bar while the hidden apps' items disappear, as before.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu


Generated by Claude Code

claude added 25 commits October 2, 2026 14:42
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- Add the pure, tested SpacingRelaunch in holzBar/Core: every owner except
  holzBar, Control Center and MenuBarAgent, distinct and sorted; a skipped
  owner no longer ends the loop (BUG-01)
- Read the item owners through MenuBarItemProvider27 on macOS 27 and never
  quit MenuBarAgent (BUG-03)
- Collect failures from the task group's results instead of mutating a
  captured array
- Add the HolzBarCore and HolzBarCoreTests targets to the test-only package

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- waitUntil returns true when the event happens, false at the timeout and
  false at once when cancelled; quitTimeout is 10 seconds
- SpacingRelaunch gets stubs so the tests compile and fail on their
  assertions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- SpacingRelaunch.waitUntil races the event against the timeout in a task
  group: true when the event happens, false at the timeout or at once when
  cancelled; it always returns and polls nothing
- quitTimeout is 10 seconds; an app still running then is left alone and
  named in the alert, never force terminated (BUG-02)
- quit(_:) observes isTerminated through key-value observation and waits
  through waitUntil; the Combine sink, the checked continuation and the
  force-termination fallback are gone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- SUMMARY with PR #35, CI results (124 tests) and the open human checks
- BUG-01, BUG-02 and BUG-03 marked complete; roadmap and state updated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- Move the Modifiers type, canonicalOrder and symbolicValue into the
  pure holzBar/Core/Modifiers.swift; the AppKit and Carbon conversions
  stay in holzBar/Hotkeys/ModifierFlags.swift
- Add Modifiers.Rejection and a stub rejection(refusesOptionOnly:)
- Add the Modifiers Swift Testing suite

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- Modifiers.rejection(refusesOptionOnly:) refuses no modifier, Shift
  alone and, on macOS 15 and later, Option or Option and Shift alone
- The recorder shows an alert that says why and to add Command or
  Control, and keeps recording; the system-reserved alert gets a message
- HotkeyRegistry logs the reason instead of calling RegisterEventHotKey
  for an Option-only combination; the hotkey signature is unchanged
- Fix the colon spacing in the ModifierFlags initializers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- waitForPermission() returns a Bool from its own AsyncStream, so any
  number of waits can run at once and each one ends
- A grant ends every pending wait with true; stopCheck() ends them
  with false, and a cancelled task returns at once
- The Grant buttons only reopen the permissions window on true
- No stored single waiter and no checked continuation any more

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- Drop cask_renames.json and the former-name app, quit and zap entries
- The release workflow only updates version and sha256
- The cask workflow runs brew style and brew audit from the local tap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
…ntry

- Import settings only from the original Ice
- Accept only holzbar:// URLs (ice-bar stays as a shelf alias)
- An unknown stored icon image set decodes as the default one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- README drops the section for users of the former name and the gallery caption
- NOTICE, CLAUDE.md and the Raycast README name only holzBar and Ice
- Past release notes use the holzBar tap, cask, app and URLs
- CLAUDE.md forbids the former name; a former-name job in build.yml enforces it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- Shared/CodeSigning/CodeSignature.swift reads the team of this process and the
  signing identifier and per-slice code directory hashes of code on disk
  (Security only, so the app keeps launching on macOS 14.0)
- Package.swift compiles it as SharedCodeSigning with a test target
- The CodeSignature suite checks the hashes against a running process with
  SecCodeCheckValidityWithProcessRequirement

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
SecCodeCopySelf's code made SecCodeCheckValidityWithProcessRequirement return errSecParam (-50) on the runner. The test now looks the process up by its audit token and logs the compared hashes, the running cdhash and SecTask's verdict when it fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- On macOS 26 the listener requires holzBar's signing identifier and one of
  the code directory hashes of the app it is embedded in when the service has
  no team (ad hoc), and the same team plus the identifier otherwise
- The listener does not listen when that requirement cannot be built (fail
  closed); the app then looks up source processes itself
- The app sets its same-team peer requirement only when it has a team
- MenuBarItemService.appIdentifier names the only app the service answers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
The app comment explains why lightweight code requirements are not used there without naming the module, so the module name appears only in MenuBarItemService/Listener.swift among the app and service sources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- getEventSource(with:) looks up, creates and stores its sources inside one
  OSAllocatedUnfairLock (withLockUnchecked, as CGEventSource is not Sendable)
- concurrent moves and clicks can no longer race on the static cache (BUG-04)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
…ange

- every drawn item (0, 2, 6, 8) is allowed
- the allowlist is exactly 0 through 127, the highest measured number
- nothing beyond the measurement is offered

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- SystemItems27 (Core) holds the drawn numbers, the highest measured number
  (127) and the allowlist derived from them, 0 through 127
- MenuBarAssessmentAssertion27 builds its configuration from
  SystemItems27.allowed; its comment now says what the code does and how
  jordanbaird/Ice#1001's 0 to 63 relates, instead of claiming they match (BUG-08)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
- 02-04-SUMMARY.md with the allowlist decision (D-03), CI result and the
  phase's open human checks
- STATE, ROADMAP and REQUIREMENTS (BUG-04, BUG-08 complete)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSzFQk1ZzGXFVBDZMYq8eu
@holzcloud
holzcloud marked this pull request as ready for review October 2, 2026 16:25
@holzcloud
holzcloud merged commit 6e4004b into main Oct 2, 2026
5 checks passed
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.

2 participants