Skip to content

macOS 26 (Tahoe) compatibility — backport community fixes + new bug fixes + CI - #1

Draft
emindeniz99 wants to merge 25 commits into
macos-26from
claude/fix-macos26-compatibility-Ar8K6
Draft

macOS 26 (Tahoe) compatibility — backport community fixes + new bug fixes + CI#1
emindeniz99 wants to merge 25 commits into
macos-26from
claude/fix-macos26-compatibility-Ar8K6

Conversation

@emindeniz99

Copy link
Copy Markdown
Owner

Draft PR opened so we can watch CI run.

Community PR backports

PR Author Status
jordanbaird#903 tabossert Frame-based control item matching, NavigationSplitView .id(), XPC ad-hoc bypass
jordanbaird#911 tducp015 SourcePID 30s negative cache, Ice control item title recognition, multi-point AX probe with parent walk
jordanbaird#922 heyalchang CGSCopyActiveMenuBarDisplayIdentifier nil → CGMainDisplayID() fallback
jordanbaird#923 heyalchang Spacing apply breakcontinue, double-resume guard, replacedPID
jordanbaird#928 hkfi Screen recording: positive-only cache, multi-URL settings opener, relaunch button + UI copy
jordanbaird#933 AlexandrosAlexiou isMouseInsideOverlayAboveMenuBar gate on show-on-click
jordanbaird#874 mp-s9i Image cache bounds fallback + tolerance

Self-authored fixes

CI

.github/workflows/build.yml:

  • swift-parse on Ubuntu 24.04, downloads Swift 6.0.3, runs swiftc -parse -enable-bare-slash-regex on every Swift file under Ice/, MenuBarItemService/, Shared/. Cheap; runs on free runners.
  • build matrix macos-26 + macos-15, code signing fully disabled, builds the Ice and MenuBarItemService schemes. Logs uploaded as artifacts; errors greppped to job output.

Both jobs trigger on every push to a feature branch and on every PR.

Remaining open issues — not fixed here


Generated by Claude Code

claude added 25 commits April 18, 2026 10:16
Ice's macos-26 branch left several critical issues that prevent the app
from working correctly on Tahoe, especially 26.3+ / 26.4+. This change
folds in the fixes from the three community PRs that address them (jordanbaird#903,
jordanbaird#911, jordanbaird#922) and the cache-thrash guard from jordanbaird#874.

Changes:

* `Bridging.getActiveMenuBarDisplayID()` falls back to `CGMainDisplayID()`
  when `CGSCopyActiveMenuBarDisplayIdentifier` returns nil, which is now
  the case on macOS 26.4.1. Without the fallback, the item cache's
  `displayID` stayed `nil` and the layout preview rendered as "Unable to
  display menu bar items" even though items existed.

* `MenuBarItemTag.Namespace` on macOS 26 recognizes Ice's own control
  items by window title ("Ice.ControlItem.*") and falls back to the
  owning application's identifiers when the owner isn't Control Center.
  This stops the UUID-namespace feedback loop that left the layout stuck
  on "Loading menu bar items..." after Control Center re-parented Ice's
  status items.

* `SourcePIDCache` caches failed AX lookups for 30 seconds, and the item
  manager no longer invalidates the window-ID cache when `sourcePID` is
  nil. Those two together stopped the thrash where each failed lookup
  triggered another full scan, racing with `IceBarPanel.show()` and
  causing clicks to drop roughly half the time on notched Macs.

* `AXHelpers.menuBarElement(nearDisplayOrigin:)` probes several inset
  points along the leftmost menu bar region instead of hit-testing the
  exact display corner. Single-point probing fails on notched displays
  (outside the rounded-corner mask), next to menu bar accessories such
  as NotchNook, and on Tahoe's translucent menu bar.
  `getApplicationMenuFrame()` and `hasValidMenuBar(in:for:)` both use
  the new helper.

* `MenuBarItemImageCache.compositeCapture` falls back to `item.bounds`
  when `CGSGetScreenRectForWindow` fails and tolerates a one-pixel
  discrepancy in the composite width. This keeps items that Control
  Center has re-parented from being dropped entirely.

* XPC `.isFromSameTeam()` requirement is only applied when the current
  process actually has a team identifier. Ad-hoc signed builds (the
  default when no signing team is configured) do not, and the old
  requirement refused every peer, leaving the `MenuBarItemService`
  unusable. A new `CodeSignInfo` helper inspects the process's code
  signature.

* The settings detail pane is keyed by the current navigation
  identifier on macOS 26 so that `NavigationSplitView` reliably updates
  on the first sidebar click.
Follow-up to the macOS 26 compatibility commit, addressing four more
issues reported against Tahoe:

* jordanbaird#906 "App opens in dock when expanding the bar" — `hideApplicationMenus()`
  used to flip the activation policy to `.regular`, which on Tahoe
  manifests as a visible dock icon every time the bar expands. Accessory
  apps can already steal the menu bar via a plain `activate`, so drop
  the policy change and leave Ice as `.accessory` throughout.

* jordanbaird#908 "System freeze after Missing control item" — when Control Center
  re-parents Ice's own control items on macOS 26 the hidden control item
  can momentarily drop out of the menu bar window list. The old handler
  replaced the cache with an empty one, which immediately triggered
  another re-cache via observers, and in pathological cases spiralled
  into a FrontBoard scene-request storm that took the whole compositor
  down. Keep the previous cache and wait for the next scheduled tick.

* jordanbaird#914 "Ice Bar auto-hides before cursor reaches it" — Tahoe synthesises
  `activeSpaceDidChange` / `didChangeScreenParameters` notifications when
  the menu bar redraws after the panel is ordered front, causing an
  immediate auto-hide. Record a `lastShowTimestamp` and ignore those
  events during a 0.5s grace period.

* jordanbaird#918 "Frame check timed out for Intego ONE" — the per-item move-event
  timeout cap of 150ms was too short for some third-party helpers on
  macOS 26 whose windows take several hundred ms to update their bounds
  after a mouse-down. Raise the cap to 500ms so the learned timeout has
  room to grow when an item is slow to respond.
On macOS 26, when Control Center re-parents Ice's hidden control item, the
$frame / $screen publishers can briefly emit nil before the reparented
window settles. If the user showed the Ice Bar just before the reparent,
the observer here would immediately call hide() and the bar would vanish.
Reuse the existing autoHideGracePeriod to swallow those transient nils.
…rd#928)

Three follow-on changes inspired by hkfi's PR jordanbaird#928 against the macos-26
branch. All of them stem from System Settings behaving less predictably
on macOS 26, and from the Screen Recording permission now sometimes
needing the app to relaunch before capture actually starts working:

* ScreenCapture.cachedCheckPermissions now only caches a *positive*
  result. Previously, if the first check returned false (the common
  case on first launch before the user has granted permission) the
  false was cached for the lifetime of the process, so granting the
  permission later had no visible effect until the user quit and
  reopened Ice.

* AppState no longer stops permission timers at the end of setup. macOS
  26 lets the user revoke or grant Screen Recording while Ice is
  running; stopping the timers meant Ice never noticed the change.

* Permission.openSettingsPane tries multiple URLs and falls back to
  /usr/bin/open if NSWorkspace.open returns false. On macOS 26 the
  anchored `...?Privacy_ScreenCapture` URL is sometimes ignored and
  the bare Security pane URL is more reliable; if both fail we shell
  out so the user still lands in System Settings.
The space/screen-parameters Publishers.Merge I added the grace-period
check to was calling hide() (which closes an NSPanel) from whatever
thread posted the underlying Notification. Notifications from
NSWorkspace and NSApplication are generally posted on main, but the
contract isn't absolute, and reading lastShowTimestamp from a non-main
thread is a data race. Add an explicit receive(on: .main) so the sink
is always serialised with the rest of the IceBarPanel's state.
I could only read the descriptions of the community PRs before; after
fetching the actual .patch files I realised my implementation had
real gaps. Fill them in:

* AXHelpers.menuBarElement walks the AX parent chain up to 4 hops
  when a probe point lands on a menu-bar item instead of the menu
  bar itself. Without this, hits on "File"/"Edit"/etc. were dropped
  as not-a-menu-bar. (PR jordanbaird#911)

* MenuBarItem / MenuBarItemTag / Namespace accept an optional
  titleOverride. On some macOS 26 builds Control Center strips the
  titles off reparented status item windows entirely, so the title
  prefix check I added earlier doesn't match anything. The caller
  now frame-matches against live NSStatusItem windows and passes
  the correct "Ice.ControlItem.*" back in. MenuBarItemManager.cacheItemsRegardless
  builds that map by converting the ControlItem window frames from
  Cocoa to CG screen coordinates and looking them up in the current
  CGWindowList. Items without a precomputed identifier fall through
  to the existing title-prefix / owner-bundle / UUID chain. (PR jordanbaird#903)

* Permission now carries a list of settingsURLs and a
  mayRequireRelaunch flag. ScreenRecordingPermission gets three
  URLs — the new macOS 26 PrivacySecurity extension URL with and
  without the ?Privacy_ScreenCapture anchor, plus the legacy
  com.apple.preference.security URL. openSettingsPane launches
  System Settings first (so the URL isn't ignored when it's cold),
  then walks the URL list via NSWorkspace, and finally shells out
  to /usr/bin/open. (PR jordanbaird#928)

* AppState.relaunch reopens the current bundle via
  NSWorkspace.openApplication and terminates the current process.
  PermissionsView, AdvancedSettingsPane, and the Menu Bar Layout
  pane expose a "Relaunch Ice" button and explanatory copy when a
  permission has mayRequireRelaunch == true. (PR jordanbaird#928)
Apply ac/sourcetree's three fixes to MenuBarItemSpacingManager
verbatim from the upstream patch:

1. applyOffset's per-PID guard uses continue instead of break, so
   hitting Control Center / the Ice process / a stale
   NSRunningApplication no longer silently aborts the whole apply.
   Each skip also logs the reason at debug level.

2. signalAppToQuit's continuation is guarded with a
   tryClaimOnce()-style OSAllocatedUnfairLock<Bool> helper so the
   force-terminate failsafe and the KVO observer can't both call
   resume and trap. A 1-second failsafe inside the timeout task
   makes sure we resume even if KVO never fires post-terminate.

3. launchApp takes a replacedPID and excludes it from the
   'already running' check, so auto-respawning helpers (Login
   Items, launchd-managed agents) that come back under a new PID
   are still recognized as fresh rather than matching the dead
   process.
Four more call sites still went straight to MenuBarItem.getMenuBarItems
without building the frame-based control-item map, so on macOS 26 their
items.first(matching: .hiddenControlItem) and friends would not have
found anything after Control Center reparented Ice's windows.

Add MenuBarItemManager.menuBarItems(on:option:) which fetches the raw
windows, builds the controlItemMap, and then asks MenuBarItem to turn
the pair into items. Point the following through it:

* temporarilyShow's destination lookup (needs .hiddenControlItem).
* rehideTemporarilyShownItems' per-context rehide path.
* LayoutBarPaddingView.performDragOperation's 1-item drag target
  lookup (needs .hiddenControlItem / .alwaysHiddenControlItem).
* MenuBarManager.hideApplicationMenus' leftmost-item scan.

MenuBarItemSpacingManager is intentionally left alone: it only reads
sourcePID/ownerPID and already guards Control Center and self, so the
control-item tag does not matter for its purposes.
Three indentation_width warnings on my own code from running SwiftLint
locally against Ice's .swiftlint.yml — multi-line if/else-if chains
that used column-aligned continuation (leading spaces matching the
opening 'if') instead of Ice's preferred style (keyword on its own
line, each condition indented one level, closing brace on its own
line). Match the existing pattern used elsewhere in the codebase.

Verified with:
  swiftc 6.0.3 -parse  (all 22 modified files clean)
  swiftlint 0.57.1     (no warnings/errors on my code; the 3
                        pre-existing empty_count errors on integer
                        counts in MenuBarItemManager remain from
                        origin/macos-26)
* .github/workflows/build.yml runs on every push/PR.

  - swift-parse: Linux job. Installs Swift 6.0.3 and runs
    `swiftc -parse -enable-bare-slash-regex` on every .swift file in
    Ice / MenuBarItemService / Shared. Catches raw syntax errors
    cheaply on free runners before we burn macOS minutes.

  - build: matrix over macos-26 (has the macOS 26 SDK and Xcode 26)
    and macos-15 (sanity check that we haven't quietly leaked a
    macOS 26-only API into an unguarded code path). Both build the
    Ice and MenuBarItemService schemes in Debug with code signing
    fully disabled, since we don't have Jordan's team identifier on
    CI runners. Logs uploaded as artifacts on every run, errors
    grepped out and surfaced in the job output.

* HIDEventManager.isMouseInsideEmptyMenuBarSpace now also rejects
  "an overlay whose level is above the menu bar at the cursor". This
  is AlexandrosAlexiou's PR jordanbaird#933, ported to the current codebase
  (the file was renamed EventManager -> HIDEventManager and the
  helper namespace MouseCursor -> MouseHelpers in the macos-26
  branch refactor; the WindowInfo helper for getting on-screen
  windows is also named differently here). Without it,
  show-on-click toggles a section when the click was meant for a
  third-party HUD/notification overlay drawn above the menu bar.
The first run revealed that the Ice codebase unconditionally calls the
macOS 26-only XPCListener(service:requirement:incomingSessionHandler:)
initializer, so Xcode 16 (the default on macos-15) cannot build it even
with @available guards — the SDK headers simply do not define that
overload. Both runners ship with Xcode 26 installed, so explicitly
xcode-select to it. macos-15 stays in the matrix as a 'older host OS,
newer SDK' canary.
The previous attempt hard-coded /Applications/Xcode_26.app, but the
GitHub runners only ship point-version-suffixed apps (Xcode_26.0.app,
Xcode_26.1.app, etc.). Glob the directory and take the highest
version sorted lexically.
Both .github/workflows/build.yml and .github/workflows/lint.yml ran
actions whose default entrypoint is still Node 20 (actions/checkout,
actions/upload-artifact, action-swiftlint). GitHub announced that
Node 20 is removed from runners on 2026-09-16; opt every job into
Node 24 today via FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true so the
warnings disappear and we don't get a hard break later. Also bump
the lint workflow's checkout pin from @V3 to @v4.
Researched what's actually current:

  actions/checkout       v4   -> v6   (v6.0.2 latest, native Node 24)
  actions/upload-artifact v4 -> v7    (v7.0.1 latest, native Node 24)

Both are native node24 in their action.yml, so the
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 escape hatch is no longer needed
and is dropped from build.yml. norio-nomura/action-swiftlint runs in a
Docker container, so the Node version doesn't apply to it.

This silences the "Node 20 actions are deprecated" annotations and
hardens both workflows against the 2026-09-16 Node 20 removal.
…a11y amber)

* Updates.swift — when Sparkle wants to show a scheduled update
  prompt while Ice is in the background, activate the app with a
  regular policy and return `true` so the dialog comes up in front
  with key focus. Previously we returned `false` and Sparkle put the
  dialog up anyway, with Ice still .accessory — on macOS 26 the
  window then refused to accept clicks, which is the cluster of
  reports in jordanbaird#912 / jordanbaird#926 / jordanbaird#931 / jordanbaird#932 / jordanbaird#937. Same fix is applied
  to `standardUserDriverWillHandleShowingUpdate` for the
  user-initiated path. (Adapted from arifim's PR jordanbaird#945.)

* PermissionsView.swift — replace plain `.yellow` on the
  "Continue in Limited Mode" button with a darker amber
  (sRGB 0.75/0.45/0). The original failed accessibility contrast
  against the light button background. (Adapted from aramb-dev's
  PR jordanbaird#942 — the asset-catalog refinement in later commits of that
  PR is deferred since the current Xcode-26 sync of the project
  doesn't include the new colorset directory.)

Skipped this round:

* PR jordanbaird#940 (lilaflo) is an 80-commit re-application of
  jordanbaird/macos-26 onto another branch — same commits we
  already carry, nothing new.

* PR jordanbaird#944 (aathanwwt) targets CompactSlider 2.x's removed
  `gestureOptions:` parameter. Package.resolved locks us at 1.2.1
  with the upper bound at <2.0, so the parameter still exists in
  the version we ship.

* PR jordanbaird#941 (lixiaoning) notch-auto-hide. The diff references
  `MenuBarItem.info`, `MenuBarItemTag.iceIcon`,
  `controlItem.state == .hideItems`, and several private flags
  (`isMovingItem`, `isMouseButtonDown`, `tempShownItemContexts`,
  `itemMoveCount`) that don't exist in the macos-26 refactor of
  MenuBarItemManager. Adapting it would mean rewriting the helper
  against a different cache architecture, which I shouldn't do
  without a notched display to verify against. The negative
  windowNumber guard in the same PR is a no-op for us because we
  don't read `NSWindow.windowNumber` anywhere in the macos-26
  branch.
Reference:
  https://astral.sh/blog/open-source-security-at-astral
  https://thehackernews.com/2026/05/mini-shai-hulud-worm-compromises.html

Concrete hardening applied to .github/workflows/build.yml and
.github/workflows/lint.yml:

1. **All third-party actions pinned to a 40-char commit SHA**, with the
   human-readable version kept as a trailing comment so Dependabot can
   still update them. A SHA is immutable; a tag isn't. Resolved:

     actions/checkout            v6.0.2  -> de0fac2e4500dabe0009e67214ff5f5447ce83dd
     actions/upload-artifact     v7.0.1  -> 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
     norio-nomura/action-swiftlint 3.2.1 -> 9f4dcd7fd46b4e75d7935cf2f4df406d5cae3684

2. **Default-deny GITHUB_TOKEN permissions**: every workflow now starts
   with `permissions: {}` at the top level. Only the swiftlint job opts
   back in (`checks: write`) to post lint annotations. The build / parse
   jobs need nothing — `upload-artifact` uses the Actions storage API,
   not the contents API.

3. **Strict bash by default**: `defaults.run.shell: bash -euo pipefail
   {0}` so a piped failure or unset variable kills the step.

4. **`upload-artifact` if-no-files-found: warn** (was `ignore`) so a
   silently empty artifact doesn't paper over a broken build, without
   failing the run outright.

5. **Verified absent**: `pull_request_target`, `workflow_run`, and any
   `${{ github.event.* }}` / `${{ github.head_ref }}` injection sinks
   inside `run:` / `with:` blocks — none present, none introduced. These
   are the recurring entry point for Shai-Hulud-style worms.

Phase-1 IoC scan was clean (no `router_init.js`, no obfuscated JS, no
exfiltration endpoints, no committed tokens, no unexpected commit
authors) so this commit is hardening-only, not incident response.
Reference:
  https://astral.sh/blog/open-source-security-at-astral

Two ecosystems registered, both on a weekly cadence:

* github-actions / — covers actions/checkout, actions/upload-artifact,
  norio-nomura/action-swiftlint (the third-party actions used in
  .github/workflows/build.yml and lint.yml, all pinned to SHA in the
  previous commit). Updates are grouped under "actions: { patterns:
  ["*"] }" so we get one PR per week, not five.

* swift / — covers the six XCRemoteSwiftPackageReference entries in
  Ice.xcodeproj: Sparkle, LaunchAtLogin-Modern, AXSwift, CompactSlider,
  Ifrit, Semaphore. The `swift` ecosystem key is GitHub's documented
  identifier for SPM dependencies (SemVer v5).

Pull-request budget capped at 5 per ecosystem; opens labelled
`dependencies` + `github-actions` / `swift` so they're easy to filter
in the issues UI.
zizmor (https://github.com/woodruffw/zizmor) is Astral's static
analyzer for GitHub Actions. It catches expression injection,
dangerous triggers, missing permissions, unpinned actions, and the
rest of the supply-chain-relevant misconfigurations that the Astral
playbook (https://astral.sh/blog/open-source-security-at-astral)
recommends auditing for.

The manual Phase-2 hardening pass already cleared the existing
build.yml / lint.yml, but this recurring job catches regressions
when someone (human or AI) adds a new workflow later — which is
exactly the entry point the Mini-Shai-Hulud worm rides
(https://thehackernews.com/2026/05/mini-shai-hulud-worm-compromises.html).

Run mode: --persona=auditor --format sarif, output uploaded to
GitHub code scanning so findings show up in the Security tab without
being lost in CI logs. `continue-on-error: true` during the initial
rollout so existing findings (if any) don't block PRs; flip it off
once the backlog is triaged.

Actions pinned to SHA, with the human-readable version as a trailing
comment so Dependabot can still update them:

  actions/checkout                       v6.0.2                   -> de0fac2e4500dabe0009e67214ff5f5447ce83dd
  astral-sh/setup-uv                     v8.1.0                   -> 08807647e7069bb48b6ef5acd8ec9567f424441b
  github/codeql-action/upload-sarif      codeql-bundle-v2.25.4    -> bc0b696b4103f5fe60f15749af68a046868d511a

Workflow itself runs with permissions: {} at the top level; only the
zizmor job opts back in to `security-events: write` (for SARIF upload)
and `contents: read` (for checkout). `persist-credentials: false` on
checkout so the runner's GITHUB_TOKEN is not left on disk for
subsequent steps.
No-op header comment update to force the build.yml / lint.yml / zizmor.yml
workflows to re-run on this PR. The previous three pushes only touched
`.github/` paths that build.yml's path filter intentionally excludes
(we don't rebuild macOS on Dependabot config changes), and the
zizmor.yml workflow that was just added didn't fire on the same commit
that introduced it. This Swift change brings them all back into scope.
SECURITY-REVIEW.md summarizes the hardening pass against branch
claude/fix-macos26-compatibility-Ar8K6, organized into the five phases
recommended by Astral's open-source security guide:

  Phase 1 — IoC compromise scan (clean: no router_init.js, no
            Session/webhook exfil endpoints, no committed secrets, no
            risky publish actions, no pull_request_target /
            workflow_run triggers, no expression injection sinks).

  Phase 2 — Workflow hardening: SHA-pinned every third-party action
            with the human-readable version as a trailing comment,
            default-deny permissions:{} at workflow level with
            narrowest per-job override, strict bash defaults, header
            blocks documenting the policy.

  Phase 3 — Repo-level controls (admin checklist that the maintainer
            needs to apply in GitHub Settings: branch protection, tag
            rulesets, secret scanning + push protection, code scanning).

  Phase 4 — Dependabot for github-actions + swift ecosystems on a
            weekly cadence.

  Phase 5 — zizmor static analysis with SARIF upload to GitHub code
            scanning.

References cited in the report:
  https://astral.sh/blog/open-source-security-at-astral
  https://thehackernews.com/2026/05/mini-shai-hulud-worm-compromises.html

Includes a verification checklist for the maintainer and a commit map
linking every concrete change to its SHA on this branch (c3f02f5,
6abfa1a, 23ec544, dda0bd4).
## Issue jordanbaird#946 — macOS 26.5 hidden items collapse into always-hidden

On macOS Tahoe 26.5, items the user previously placed in the hidden
section land in the always-hidden section instead, and dragging them
back doesn't stick.

Subagent investigation traced the cause to a likely Control Center
re-parenting regression that briefly reports the two control items with
overlapping/inverted frames. With overlapping bounds, the section
predicates in `CacheContext.findSection` make `.hidden` unsatisfiable
(`itemBounds.maxX <= hiddenControlItemBounds.minX &&
 itemBounds.minX >= alwaysHiddenControlItemBounds.maxX` can never both
hold when the always-hidden bounds touch or cross the hidden bounds),
so every formerly-hidden item falls through to `.alwaysHidden`. That
exactly matches the reported symptom.

Two defensive changes (Swift):

* `CacheContext.alwaysHiddenControlItemBounds` is now a computed
  property that returns `nil` whenever the raw bounds are not strictly
  to the left of `hiddenControlItemBounds`. The existing no-always-
  hidden predicate branches in `findSection` then take over, which
  classify every left-of-hidden item as `.hidden`. The user's hidden
  customization is preserved through the bad cache tick; the next
  tick re-derives bounds and, once the control items have settled,
  always-hidden starts working again.

* `enforceControlItemOrder` now reads control-item bounds via
  `Bridging.getWindowBounds(for:)` instead of the stale CGWindowList
  snapshot in `item.bounds`. The two can disagree for a few hundred
  ms during a Control Center re-parent; without the live read, this
  routine occasionally "corrected" an order that was already right
  according to the live coordinates.

Both changes degrade gracefully if the macOS 26.5 hypothesis is
wrong: worst case is one extra `.warning` log per cache tick. Still
needs hardware verification before claiming the regression is fixed.

## Phase 5 expansion — gitleaks and OSV-Scanner

`.github/workflows/gitleaks.yml` runs gitleaks against the full git
history (fetch-depth: 0, persist-credentials: false) on every push,
PR, weekly Monday schedule, and workflow_dispatch. SARIF uploaded to
GitHub Code Scanning. `continue-on-error: true` during rollout.

`.github/workflows/osv-scanner.yml` calls the upstream
`osv-scanner-reusable.yml` workflow against Package.resolved on the
same schedule. Path-filtered to lockfile / workflow changes for push
and PR.

All four new `uses:` references pinned to 40-char SHAs with the
human-readable version as a trailing comment so Dependabot can keep
them current:

  actions/checkout                                                       v6.0.2                 de0fac2e4500dabe0009e67214ff5f5447ce83dd
  gitleaks/gitleaks-action                                               v2.3.9                 ff98106e4c7b2bc287b24eaf42907196329070c7
  github/codeql-action/upload-sarif                                      codeql-bundle-v2.25.4  bc0b696b4103f5fe60f15749af68a046868d511a
  google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml   v2.3.8                 9a498708959aeaef5ef730655706c5a1df1edbc2

## Documentation

SECURITY-REVIEW.md updated to reflect that gitleaks and OSV-Scanner
are now part of Phase 5 (no longer in the "deliberately skipped"
section), and the new commit is added to the commit map.
…lder

The unquoted form 'shell: bash -euo pipefail {0}' is technically valid
YAML but the GitHub Actions workflow parser appears to have stopped
firing build.yml / lint.yml / zizmor.yml etc. on every push since the
hardening commit. The {0} placeholder reads as the start of a flow
mapping to stricter parsers. Quoting it ("bash -euo pipefail {0}")
keeps the same semantics in Actions and makes the file unambiguous.
The unchecked `as! NSBezierPath` in the shadow-drawing helper has been
correlated with EXC_BREAKPOINT crashes on the main thread on macOS 26.5
(see jordanbaird#956). The runtime invariant 'NSBezierPath.copy()
returns NSBezierPath' has held in practice, but the safe form costs
nothing: switch to `guard let path = copy() as? NSBezierPath else
{ return }` so a runtime mismatch becomes a no-op draw instead of
trapping the process.

The displayID half of PR jordanbaird#956 is intentionally skipped: the patch
introduces a syntax error
(`deviceDescription((deviceDescription[…]` is unbalanced).
The jordanbaird#946 defensive guard turned alwaysHiddenControlItemBounds from a
stored `lazy var` into a computed property. Its getter reads two `lazy
var` backing properties (rawAlwaysHiddenControlItemBounds and
hiddenControlItemBounds); accessing a lazy stored property mutates
`self`, so a non-mutating computed getter on a struct fails to compile:

  error: cannot use mutating getter on immutable value: 'self' is immutable

This was not caught earlier because `swiftc -parse` only checks syntax
and the macOS build CI stopped firing after d23a15f, so the
type-checking phase never ran on this commit. Reproduced and fixed
locally with a minimal Swift 6.0.3 typecheck:

  - non-mutating computed getter reading lazy vars -> error (confirmed)
  - `mutating get` -> compiles, and the sole caller findSection(_:)
    is already `mutating func`, so the call sites are unaffected.

Mark the getter `mutating get`.
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