Skip to content

fix(tauri): correct malformed fixture CSP (root cause) + harden macOS DirectEval against WebKit code-4 (#540) - #552

Merged
goosewobbler merged 10 commits into
mainfrom
fix/tauri-macos26-app-nap
Jul 28, 2026
Merged

fix(tauri): correct malformed fixture CSP (root cause) + harden macOS DirectEval against WebKit code-4 (#540)#552
goosewobbler merged 10 commits into
mainfrom
fix/tauri-macos26-app-nap

Conversation

@goosewobbler

@goosewobbler goosewobbler commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Addresses the macOS-26.4 embedded-DirectEval flake (#540) — browser.tauri.execute in standalone/deeplink/package modes. Root-caused from the WebKit unified log + local reproduction.

1. Root cause — malformed fixture CSP (the real fix)

Both Tauri fixtures (fixtures/e2e-apps/tauri and fixtures/package-tests/tauri-app) used connect-src: ["ipc:*", "plugin:*", "tauri:*"]. The stray * makes them malformed CSP scheme-sources that don't match the IPC custom-protocol URL (macOS/Linux fetch ipc://localhost/<cmd>; Windows/Android http://ipc.localhost). WebKit blocked that fetch, so Tauri sticky-fell-back to the postMessage IPC interface — whose response is delivered by eval-ing into the webview and only completes while the run loop is pumping. On the idle headless macos-26.4 runner it isn't, so core.invoke stalled and DirectEval timed out. Locally a real display keeps the loop pumping, which is why it never reproduced — the custom-protocol failure was present locally too (66×/run).

Fix: ipc: / plugin: / tauri: (+ http://ipc.localhost). Verified as a controlled A/B on CI: with the e2e fixture's CSP fixed, every DirectEval macOS leg went green and IPC-custom-protocol failures dropped to 0 on the runner, while the package-test leg — CSP still malformed — failed with the same Script execution timed out stall; fixing its CSP greened it too.

2. Harden the DirectEval result path against WebKit code-4 (defense-in-depth)

Independently, macOS 26.4's WebKit intermittently reclaims callAsyncJavaScript's completion handler (Completion handler for function call is no longer reachable, WKError code 4). This delivers the DirectEval result out-of-band instead, mirroring the Windows AsyncScriptState native-handler path: EvalResultRegistry (eval_channel.rs, keyed oneshot) + a wdioEvalResult WKScriptMessageHandler (macos.rs); execute_direct_eval runs the script via callAsyncJavaScript (to pump) but takes the result from the message channel, so a reclaim is harmless. macOS-only.

Residual — tracked separately

A residual intermittent stall remains on the off-screen headless 26.4 runner: with nothing driving the run loop, WebKit doesn't promptly schedule work (page-load / eval), so a DirectEval leg can still occasionally time out. This is environmental (no display → idle run loop), re-runnable, and unrelated to the two fixes here. A follow-up PR attempts a run-loop-pump mitigation; the WebKit-log diagnostics are retained on this branch for that work.

Refs #540

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Standing release PR: #456 · 15 packages queued · open 34d 12h 8m · ✅ ready to merge

Release Preview — 17 packages

Note: Labels on this PR are advisory in standing-pr mode. Bumps come from conventional commits in the standing PR; override by editing labels on the standing PR itself. Add release:immediate to bypass the standing PR and release this PR directly.

These changes will be added to the release PR (#456) when merged:

Changelog

@wdio/dioxus-bridge 1.0.0-next.3 → 1.0.0-next.4

Changed

  • Update version to 1.0.0-next.4
@wdio/dioxus-service 1.0.0-next.3 → 1.0.0-next.4

Changed

  • Update version to 1.0.0-next.4
wdio-dioxus-driver 1.0.0-next.3 → 1.0.0-next.4

Changed

  • Update version to 1.0.0-next.4
wdio-dioxus-embedded-driver 1.0.0-next.3 → 1.0.0-next.4

Changed

  • Update version to 1.0.0-next.4
@wdio/tauri-plugin 1.2.0 → 1.2.1

Changed

  • Update version to 1.2.1
@wdio/tauri-service 1.2.0 → 1.2.1

Changed

  • Update version to 1.2.1
tauri-plugin-wdio-webdriver 1.2.0 → 1.2.1

Changed

Fixed

  • deliver macOS DirectEval results out-of-band via a message channel (tauri)
  • return the DirectEval promise directly (drop await) to avoid WKError 4 (tauri)
  • route DirectEval through callAsyncJavaScript (the working session path) (tauri)
  • back off DirectEval result-polling to end the macOS 26.4 hang (tauri)
  • disable WebView occlusion detection to stop the macOS 26.4 hang (tauri)
@wdio/flutter-service 1.0.0-next.1 → 1.0.0-next.2

Changed

  • Update version to 1.0.0-next.2
wdio_flutter N/A → 0.1.1

Changed

  • Update version to 0.1.1
@wdio/electrobun-service 0.1.0 → 0.1.1

Changed

  • Update version to 0.1.1
@wdio/electron-service 10.1.0 → 10.1.1

Changed

  • Update version to 10.1.1
@wdio/native-cdp-bridge 1.0.0 → 1.0.1

Changed

  • Update version to 1.0.1
@wdio/native-core 1.0.0 → 1.0.1

Changed

  • Update version to 1.0.1
@wdio/native-mobile-core 1.0.0 → 1.0.1

Changed

  • Update version to 1.0.1
@wdio/native-spy 1.1.0 → 1.1.1

Changed

  • Update version to 1.1.1
@wdio/native-types 2.4.0 → 2.4.1

Changed

  • Update version to 2.4.1
@wdio/native-utils 2.5.0 → 2.5.1

Changed

  • Update version to 2.5.1
@wdio/react-native-service 1.0.0-next.0 → 1.0.0-next.1

Changed

  • Update version to 1.0.0-next.1

After merge — predicted release

No version escalation — this PR's changes will be included in the queued release without affecting the projected versions.

Package Standing PR This PR After merge
@wdio/dioxus-bridge 1.0.0-next.4 1.0.0-next.4 1.0.0-next.4
@wdio/dioxus-service 1.0.0-next.4 1.0.0-next.4 1.0.0-next.4
@wdio/electrobun-service 0.2.0 0.1.1 0.2.0
@wdio/electron-service 10.2.0 10.1.1 10.2.0
@wdio/native-cdp-bridge 1.1.0 1.0.1 1.1.0
@wdio/native-core 1.1.0 1.0.1 1.1.0
@wdio/native-spy 1.2.0 1.1.1 1.2.0
@wdio/native-types 2.5.0 2.4.1 2.5.0
@wdio/native-utils 2.6.0 2.5.1 2.6.0
@wdio/tauri-plugin 1.3.0 1.2.1 1.3.0
@wdio/tauri-service 1.3.0 1.2.1 1.3.0
tauri-plugin-wdio-webdriver 1.3.0 1.2.1 1.3.0
wdio_flutter 0.2.0 0.1.1 0.2.0
wdio-dioxus-driver 1.0.0-next.4 1.0.0-next.4 1.0.0-next.4
wdio-dioxus-embedded-driver 1.0.0-next.4 1.0.0-next.4 1.0.0-next.4

Updated automatically by ReleaseKit

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR corrects Tauri fixture CSP sources and adds an out-of-band macOS DirectEval result channel.

  • Replaces malformed custom-protocol CSP entries in both Tauri fixtures.
  • Registers a macOS WebKit script-message handler backed by a keyed oneshot registry.
  • Routes DirectEval results through the native handler instead of relying on WebKit’s completion callback.
  • Expands macOS CI diagnostics for WebKit logs and crash reports.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains within the follow-up review scope.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/tauri-plugin-webdriver/src/platform/macos.rs Adds the macOS WebKit message handler and changes DirectEval to await out-of-band results.
packages/tauri-plugin-webdriver/src/eval_channel.rs Introduces the synchronized, keyed oneshot registry used to correlate DirectEval responses.
packages/tauri-plugin-webdriver/src/lib.rs Registers the shared macOS evaluation-result registry during plugin setup.
packages/tauri-plugin-webdriver/Cargo.toml Enables the objc2 WebKit features required for configuration and script-message handling.
fixtures/e2e-apps/tauri/src-tauri/tauri.conf.json Corrects custom-protocol CSP scheme sources and permits the IPC localhost endpoint.
fixtures/package-tests/tauri-app/src-tauri/tauri.conf.json Applies the same corrected IPC CSP configuration to the package-test fixture.
.github/workflows/_ci-e2e-tauri-all-providers.reusable.yml Replaces process sampling with WebKit log streaming and broader crash-report collection.

Sequence Diagram

sequenceDiagram
  participant Client as DirectEval client
  participant Executor as macOS executor
  participant WebKit as WKWebView
  participant Handler as wdioEvalResult handler
  participant Registry as EvalResultRegistry
  Client->>Executor: POST /wdio/eval
  Executor->>Registry: register(id)
  Executor->>WebKit: callAsyncJavaScript(wrapper)
  WebKit->>Handler: "postMessage({id, result})"
  Handler->>Registry: complete(id, result)
  Registry-->>Executor: resolve oneshot
  Executor-->>Client: evaluation response
Loading

Reviews (11): Last reviewed commit: "fix(tauri-fixture): correct malformed CS..." | Re-trigger Greptile

…4 hang

#540 root cause (confirmed via #550's sampler): on macOS 26.4 the occluded/
background CI app's WebContent process is throttled/suspended by WebKit — the
app sends RunJavaScriptInFrameInScriptWorld and never gets a reply, so
in-webview evaluateJavaScript wedges and the :4445 WebDriver server stops
responding. The main thread just idles in the Cocoa run loop; it is neither
deadlocked nor crashed.

The earlier App-Nap approach was the wrong lever (host App Nap != WebKit's
per-page WebContent throttling). Instead, disable window-occlusion detection
on the WebDriver webview so its page is always treated as visible and its
WebContent process is never throttled. Private WKWebView SPI, guarded via
respondsToSelector; logs whether it applied so CI confirms it.

Refs #540.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
@goosewobbler
goosewobbler force-pushed the fix/tauri-macos26-app-nap branch from 1710f7c to b7a2803 Compare July 27, 2026 09:26
@goosewobbler goosewobbler changed the title fix(tauri): disable App Nap on the embedded macOS app to stop the 26.4 hang (#540) fix(tauri): disable WebView occlusion detection to stop the macOS 26.4 hang (#540) Jul 27, 2026
goosewobbler and others added 7 commits July 27, 2026 11:06
…olds

The occlusion-detection fix greened all macOS embedded legs on the 26.4
runner with no captured hang (the sampler showed a healthy app, zero
stalled-eval IPC), so the background process sampler, DiagnosticReports
collection, and the standalone backend/frontend log capture added in #550
are no longer needed. Revert them.

Refs #540.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…on mitigation (#540)

The occlusion-disable fix alone did not hold — run 2 hung identically
(Script execution timed out). The earlier diagnostics had a blind spot:
they sampled/collected only the app process, never the WebContent/GPU
helpers, so 'occluded-page suspension' was inferred, not proven — the hang
is equally consistent with a WebContent-process CRASH on the headless 26.4
runner.

Discriminate + mitigate in one run:
- macos.rs: also activate the app (NSApplication::activate) alongside the
  occlusion-detection disable, in case the page is treated as background
  rather than occluded. Both guarded/benign with a real display.
- workflow: passively stream the WebKit unified log during the embedded
  step (no  that could keep the process warm) and collect crash
  reports for ALL WebKit helpers (WebContent/GPU/Networking), not just the
  app. The log says outright whether a web process crashed or was suspended.
- api.spec.ts: restore backend/frontend log capture so the plugin's
  occlusion/activation tracing lands in the artifacts.

Refs #540.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…root crash reports

The prior discriminator run captured 1.4MB of WebKit unified log but the
upload glob is e2e/logs/**/*.log and the file was webkit-log.txt, so it was
dropped. Write it as .log. Also scan /Library/Logs/DiagnosticReports (where
WebContent/GPU helper crashes land) in addition to $HOME, via sudo -n.

Both mitigations (occlusion-disable + activation) are now in the app and it
still hung, which argues against page suspension and toward a WebContent
crash or a genuine 26.4 WebKit IPC bug — this run retrieves the log that
says which.

Refs #540.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…hang

#540 root cause (from the WebKit unified log on a hung run): NOT WebContent
suspension or a crash — the log shows WebContent healthy, foregrounded, and
running runJavaScript ~60x/sec for the full 30s (1594 evals) = this poll
loop spinning. #549's fire-and-forget + 15ms poll hammers the webview, which
starves Tauri's own core.invoke response eval (delivered by eval-ing into
the SAME webview) on a slow/headless runner, so the fired script's awaited
invoke never resolves and the loop times out. Explains why only the
DirectEval legs (standalone/deeplink/package) fail while the session-path
legs (standard/window/multiremote) pass, why it's intermittent (a race),
and why it doesn't repro on a fast local machine.

Poll gently: wait 150ms before the first read and between reads, leaving the
webview free for Tauri's IPC-response eval. Reverts the disproven
occlusion-detection / app-activation mitigations.

A durable follow-up (deliver the result via a dedicated Rust command +
oneshot channel, eliminating result-polling entirely) is tracked separately.

Refs #540.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
… session path)

Definitive root cause (WebKit unified log on a hung run + the passing legs):
core.invoke('get_platform_info') works on macos-26 (26.4) via the session
path's callAsyncJavaScript, but hangs via #549's DirectEval fire-and-forget.
WebContent was healthy/foregrounded the whole time — not suspended, not
crashed — running the 15ms poll ~60x/sec for the full 30s. callAsyncJavaScript
keeps a WebKit run-loop activity alive that pumps Tauri's core.invoke response
IPC; fire-and-forget left nothing driving it, so the invoke never resolved
(independent of poll rate: 1594 @15ms and 268 @150MS both hung).

Rework execute_direct_eval to use callAsyncJavaScript — the same mechanism the
session execute_async_script path uses and that works on 26.4 — with a single
minimal wrap bridging the pre-wrapped callback-style script to the promise
contract. #549 abandoned callAsyncJavaScript only because it had routed this
script through execute_async_script's extra (double) wrap, which tripped the
'completion handler no longer reachable' reclaim; a single clean wrap, like the
session path, does not. Deletes the poll loop; leaves execute_async_script
untouched.

Refs #540.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…oid WKError 4

The callAsyncJavaScript bridge still hit 'Completion handler no longer
reachable' (code 4) because it used 'return await new Promise(...)'. The
working session execute_async_script path uses 'return new Promise(...)' —
handing the promise object straight to callAsyncJavaScript to await natively.
The extra await hop makes WebKit 26.4 lose the completion handler's
reachability. Match the session form exactly.

Refs #540.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…e channel

Root cause (WebKit unified log on hung 26.4 runs): the DirectEval result
was taken from callAsyncJavaScript's completion handler, which macOS 26.4's
WebKit reclaims intermittently ('Completion handler for function call is no
longer reachable'). callAsyncJavaScript is still needed to keep the run loop
pumping so the app's own core.invoke IPC resolves on the headless runner —
but the result must not depend on that handler.

Deliver the result out-of-band, mirroring the Windows AsyncScriptState
native-handler path: the eval wrapper posts { id, result } to a new
'wdioEvalResult' WKScriptMessageHandler, which completes a oneshot the
executor awaits (keyed by id via EvalResultRegistry). A completion-handler
reclaim is now harmless — the result already arrived over the channel. The
poll loop is gone.

macOS-only: other platforms use the trait-default execute_direct_eval
(execute_async_script) unchanged. Diagnostics retained for this validation
run; to be removed once green.

Refs #540

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
@goosewobbler goosewobbler changed the title fix(tauri): disable WebView occlusion detection to stop the macOS 26.4 hang (#540) fix(tauri): deliver macOS DirectEval results out-of-band via a WKScriptMessageHandler (#540) Jul 27, 2026
…540)

The fixture's connect-src used 'ipc:*' / 'plugin:*' / 'tauri:*' — the stray
'*' makes them malformed CSP scheme-sources that don't match the custom
protocol URLs (on macOS Tauri fetches IPC at ipc://localhost/<cmd>). WebKit
blocked that fetch, so Tauri sticky-fell-back to the postMessage IPC
interface — whose response is delivered by eval-ing into the webview and only
completes while the run loop is pumping. On the idle headless macos-26.4 CI
runner it isn't, so core.invoke stalled and DirectEval timed out (the #540
flake). Locally a real display keeps the loop pumping, which is why it never
reproduced.

Use proper scheme-sources (ipc:, plugin:, tauri:) + http://ipc.localhost for
the Windows/Android form. Verified locally: IPC-custom-protocol failures drop
66 -> 0, so core.invoke uses the fast custom protocol and needs no pumping.

Refs #540

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
@goosewobbler goosewobbler changed the title fix(tauri): deliver macOS DirectEval results out-of-band via a WKScriptMessageHandler (#540) fix(tauri): correct fixture CSP (root cause) + harden macOS DirectEval against WebKit code-4 (#540) Jul 27, 2026
Same ipc:*/plugin:*/tauri:* -> ipc:/plugin:/tauri: (+ http://ipc.localhost)
fix as the e2e fixture. CI proved the point as a clean A/B: with the e2e
fixture's CSP fixed, every DirectEval macOS leg went green and IPC
custom-protocol failures dropped to 0 on the runner, while the package-test
leg — whose CSP was still malformed — failed with the same 'Script execution
timed out' pump stall. Fixing its CSP removes the postMessage fallback here
too.

Refs #540

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
@goosewobbler goosewobbler changed the title fix(tauri): correct fixture CSP (root cause) + harden macOS DirectEval against WebKit code-4 (#540) fix(tauri): correct malformed fixture CSP (root cause) + harden macOS DirectEval against WebKit code-4 (#540) Jul 28, 2026
@goosewobbler
goosewobbler merged commit 14d8fb4 into main Jul 28, 2026
254 of 258 checks passed
@goosewobbler
goosewobbler deleted the fix/tauri-macos26-app-nap branch July 28, 2026 00:59
@github-actions github-actions Bot mentioned this pull request Jul 24, 2026
12 tasks
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