Skip to content

fix(bridge): defer dylib init off the constructor (macOS 26 dyld init-order)#138

Merged
steipete merged 2 commits into
openclaw:mainfrom
omarshahine:fix/bridge-defer-constructor-init
Jun 10, 2026
Merged

fix(bridge): defer dylib init off the constructor (macOS 26 dyld init-order)#138
steipete merged 2 commits into
openclaw:mainfrom
omarshahine:fix/bridge-defer-constructor-init

Conversation

@omarshahine

Copy link
Copy Markdown
Contributor

Summary

Move all ObjC/Foundation/IMCore work out of the injected dylib's __attribute__((constructor)) (injectedInit) into bridgeBootstrap(), dispatched on the main queue. The constructor now only enqueues (a libdispatch call, no synchronous ObjC message dispatch).

Why: macOS 26 tightened dyld initializer ordering for platform/system apps. Touching ObjC/Foundation/IMCore at constructor time can run before libSystem finishes bootstrapping ("dyld initialized but libSystem has not") and abort Messages.app on launch. Deferring onto the main queue runs the work only after the process is fully initialized.

Based on v0.11.0; constructor change only (+16/-2).

Verification

  • make build-dylib clean (arm64e).
  • Live regression test on macOS 26.4.1: built natively, injected via imsg launch --kill-only + --dylib <built>. vmmap confirmed the new dylib mapped into the live Messages process; .imsg-bridge-ready rewritten; imsg status --json -> advanced_features:true, v2_ready:true, "Connected to Messages.app"; imsg chats RPC roundtrip works; selectors identical to the released dylib (plus a bonus pollPayloadMessage:true). No crash, bridge fully up. Rolled back to the released dylib afterward.

Scope / caveat

This proves no regression on macOS 26.4.x, where injection already works. It does not prove it fixes the macOS 26.5 injection/crash path (no 26.5 host with a working bridge available to test) — that remains the motivating hypothesis. The change is sound hardening regardless and is risk-free on hosts where the dylib already loads.

…-order)

Move all ObjC/Foundation/IMCore work out of the __attribute__((constructor))
into a bridgeBootstrap() dispatched on the main queue. macOS 26 tightened dyld
initializer ordering for platform apps; touching Foundation at constructor time
can run before libSystem finishes bootstrapping and abort Messages on launch.
The constructor now only enqueues (a libdispatch call, no synchronous ObjC).

Based on v0.11.0; constructor change only.
@clawsweeper

clawsweeper Bot commented Jun 7, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed June 6, 2026, 8:20 PM ET / 00:20 UTC.

Summary
The PR moves injected dylib bootstrap work from the constructor into a main-queue bridgeBootstrap() call to avoid macOS 26 dyld initializer ordering crashes.

Reproducibility: no. high-confidence reproduction of the exact macOS 26.5 crash was established in this review. Source inspection does confirm current main performs Foundation and IMCore work inside the dylib constructor, matching the proposed failure mode.

Review metrics: 1 noteworthy metric.

  • Patch scope: 1 file changed, +16/-2. The diff is tightly scoped to the injected helper entry point, which limits the merge surface.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • A macOS 26.5 launch/status log would remove the main remaining validation gap if a reviewer has access to that host.

Mantis proof suggestion
A macOS 26.5 desktop or terminal proof would materially strengthen confidence in the motivating crash fix. A maintainer can ask Mantis to capture proof by posting a new PR comment that starts with the OpenClaw Mantis account mention, followed by:

visual task: on macOS 26.5, build this PR's helper dylib, run `imsg launch --dylib <built>`, and show Messages stays running with `imsg status --json` and `imsg chats` succeeding.

Risk before merge

  • [P1] The live proof covers macOS 26.4.1 regression behavior; it does not prove the motivating macOS 26.5 dyld/libSystem crash path is fixed on a 26.5 host.

Maintainer options:

  1. Decide the mitigation before merge
    Land the narrow constructor-deferral after normal maintainer review, preserving the existing delayed watcher startup and adding macOS 26.5 launch/status proof if a suitable host is available.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • No repair lane is needed because there is no blocking review finding; this should proceed through normal maintainer review and optional version-specific proof.

Security
Cleared: The diff only defers existing injected-helper initialization and does not add dependency, credential, permission, or artifact-loading changes.

Review details

Best possible solution:

Land the narrow constructor-deferral after normal maintainer review, preserving the existing delayed watcher startup and adding macOS 26.5 launch/status proof if a suitable host is available.

Do we have a high-confidence way to reproduce the issue?

No high-confidence reproduction of the exact macOS 26.5 crash was established in this review. Source inspection does confirm current main performs Foundation and IMCore work inside the dylib constructor, matching the proposed failure mode.

Is this the best way to solve the issue?

Yes. Deferring the existing bootstrap body behind an async main-queue hop is the narrowest maintainable fix I see, and the remaining macOS 26.5 validation gap is proof-related rather than a clear code blocker.

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 041b40686a6d.

Label changes

Label changes:

  • add P2: This is a normal-priority bridge availability hardening change for the limited advanced IMCore launch surface.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-change live bridge validation on macOS 26.4.1 with the patched dylib loaded into Messages and CLI RPC/status checks succeeding.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-change live bridge validation on macOS 26.4.1 with the patched dylib loaded into Messages and CLI RPC/status checks succeeding.

Label justifications:

  • P2: This is a normal-priority bridge availability hardening change for the limited advanced IMCore launch surface.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-change live bridge validation on macOS 26.4.1 with the patched dylib loaded into Messages and CLI RPC/status checks succeeding.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-change live bridge validation on macOS 26.4.1 with the patched dylib loaded into Messages and CLI RPC/status checks succeeding.
Evidence reviewed

What I checked:

  • Repository policy read: AGENTS.md was read fully before review; its focused-change and verification guidance applies to this PR. (AGENTS.md:1, 041b40686a6d)
  • Current main constructor behavior: Current main's injectedInit calls NSLog, NSProcessInfo, NSClassFromString, and IMDaemon selectors before the delayed main-queue initialization block. (Sources/IMsgHelper/IMsgInjected.m:4374, 041b40686a6d)
  • PR patch scope: The supplied PR diff changes only Sources/IMsgHelper/IMsgInjected.m, moving the existing constructor body into bridgeBootstrap() and leaving the constructor as a dispatch_async enqueue to the main queue. (Sources/IMsgHelper/IMsgInjected.m:4374, 58f1df57fb1b)
  • Advanced bridge contract: The repository documents imsg launch as the command that launches Messages.app with the helper dylib injected, so avoiding launch-time aborts is part of the existing advanced IMCore behavior. (docs/advanced-imcore.md:16, 041b40686a6d)
  • Contributor real-behavior proof: The PR body reports a native macOS 26.4.1 build, custom dylib injection, vmmap confirmation, ready-lock rewrite, imsg status --json bridge readiness, and an imsg chats RPC roundtrip after the change. (58f1df57fb1b)
  • Feature history: Local blame attributes the current constructor entry point and IMCore bootstrap body to the 0.11.0 release-prep commit. (Sources/IMsgHelper/IMsgInjected.m:4374, c3205e1361bf)

Likely related people:

  • Peter Steinberger: Local blame and symbol history attribute the current injected-helper constructor body to the 0.11.0 release-prep commit that added Sources/IMsgHelper/IMsgInjected.m. (role: introduced behavior; confidence: medium; commits: c3205e1361bf; files: Sources/IMsgHelper/IMsgInjected.m)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 7, 2026
@steipete

Copy link
Copy Markdown
Collaborator

Maintainer pass on PR #138 complete.

What changed beyond the original patch:

  • Kept the dylib constructor ObjC/Foundation/IMCore-free. It now performs only a libdispatch main-queue enqueue.
  • Starts the existing 2s startup delay from the first main-queue turn, not from dyld constructor time, so the bootstrap cannot become eligible before Messages first services the main queue.
  • Runs bridge bootstrap exactly once under dispatch_once and an explicit @autoreleasepool.
  • Leaves the ready marker behavior behind startFileWatcher(), after watcher setup, and keeps v2 watcher/event observer startup in the same bootstrap boundary.
  • Added a source-shape regression test for the constructor boundary.

Proof run locally on macOS 26.5 build 25F71:

  • make lint passed. Existing warnings only; no serious lint failures.
  • make test passed: 322 tests.
  • make build passed: built bin/imsg and universal bin/imsg-bridge-helper.dylib.
  • make build-dylib passed: built .build/release/imsg-bridge-helper.dylib.
  • autoreview --mode local first found the constructor-timer timing issue; after the fix, rerun was clean with no accepted/actionable findings.

Live injection caveat:

  • Local host is macOS 26.5 build 25F71, but SIP is enabled.
  • Checked approved alternate targets: Mac Studio 26.5 25F71, clawmac 26.5.1 25F80, and Parallels macOS Tahoe 26.5 25F71. All have SIP enabled.
  • Because no SIP-disabled macOS 26.5 target was available, I could not produce a live 26.5 DYLD injection/RPC proof.
  • On the local 26.5 host, imsg status --json reported advanced_features:false and v2_ready:false; imsg launch --dylib .build/release/imsg-bridge-helper.dylib --json refused with sip_enabled before injection; Messages stayed running with the same PID, no .imsg-bridge-ready marker existed, and vmmap showed no custom dylib mapping. A redacted imsg chats --limit 1 --json smoke was also blocked by Full Disk Access for this Codex parent process, so no private content was read.

CI on pushed head 9b3bdaebf1f2e3ee6f6de99245f74c87ced7bd8e is green:

  • linux-read-core passed.
  • macos passed.
  • Socket checks passed.

@steipete steipete merged commit 65d9cee into openclaw:main Jun 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants