fix: give the injected bridge helper a single owner per container - #284
Conversation
Only one injected Messages.app may service the bridge queues. Take a kernel flock on a dedicated .imsg-bridge-owner.lock before touching any shared state; a second injected instance stands by instead of clearing the legacy IPC files, overwriting the ready marker, claiming v2 requests, or removing the owner's ready marker on exit. The standby retries once a second and takes over when the owner's flock is released, so a launch that overlaps a still-terminating owner still ends with a working bridge and no relaunch. Follow-up to openclaw#272 / openclaw#274, which serialized launches but left the helper itself unguarded. Fixes openclaw#283. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0131k87VJuatKyj7U1XJuP3p
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
@clawsweeper review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Codex review: needs changes before merge. Reviewed September 7, 2026, 1:26 PM ET / 17:26 UTC. ClawSweeper reviewWhat this changesThe PR gives each Messages container one active injected helper, adds standby takeover and readiness restoration, and covers that lifecycle with native process tests and upgrade documentation. Merge readiness⛔ Needs changes before merge - 1 item remains This remains a useful, distinct fix beyond the merged launcher lock. The updated production-path lifecycle evidence and upgrade documentation address both previous review requests; no blocking patch defect was found. Priority: P1 Review scores
Verification
How this fits togetherThe injected Messages helper receives file-based requests from imsg clients and dispatches them through the Messages bridge. Helper ownership determines which process consumes requests and publishes readiness. flowchart TD
A[imsg bridge client] --> B[Container request queue]
C[Injected helper processes] --> D{Container ownership lock}
D -->|Acquired| E[Active helper]
D -->|Busy| F[Standby and retry]
F --> D
B --> E
E --> G[Responses and readiness]
Before merge
Agent review detailsSecurityNone. Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Technical reviewBest possible solution: Use the dedicated lifetime lock alongside launcher serialization, retaining the documented stop-and-relaunch procedure for older injected helpers. Do we have a high-confidence way to reproduce the issue? Yes: current main unconditionally initializes shared IPC and removes readiness on helper exit, so two injected helpers can interfere. The supplied before-case process run corroborates that path; this review did not execute it. Is this the best way to solve the issue? Yes: a separate persistent lock avoids ready-marker inode replacement and complements the existing launcher lock; retrying contenders preserves takeover after owner exit. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning medium; reviewed against 34aa2b26afce. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
History |
If the launcher's killall misses a live owner, its cleanup deletes .imsg-bridge-ready and the replacement stands by, so nothing would write a marker again and imsg launch would time out against a bridge that is serving. The owner now restores its marker once a second while it holds the lock, so readiness stays truthful and the standby still takes over on exit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0131k87VJuatKyj7U1XJuP3p
|
Maintainer verification of
The isolated hosts load no private Messages frameworks, and no messages were sent. This establishes the changed ownership and request-servicing behavior; it does not claim live Messages injection, delivery, or mixed-version exclusion. The docs explicitly require stopping an older injected helper before upgrading. Prepared for squash landing by the orchestrator. Merge this PR before #285, which carries the sole changelog entry and thanks @omarshahine. No merge or release was performed in this verification pass. |
|
ClawSweeper status: review started. I am starting a fresh review of this pull request: fix: give the injected bridge helper a single owner per container This is item 1/1 in the current shard. Shard 0/1. This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking. Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted. |
Document the bridge ownership fix from #284 under Unreleased, with a user-facing highlight and contributor credit. Keep the native-audio change outside this release pending its existing playback-proof gate.
A second injected Messages process could clear the active helper's legacy IPC, overwrite its ready PID, and remove the surviving helper's ready marker when it exited. The launcher lock only serializes launcher calls; it does not establish helper ownership.
This change holds a dedicated kernel lock for the helper's lifetime. Standbys leave shared IPC and readiness alone, retry ownership once per second, and activate after the owner exits. The active owner restores a removed ready marker. The lock file remains in place so ownership always refers to the same inode.
Fixes #283. The reported vanished-response symptom is not independently attributed to duplicate claiming; this change repairs the verified ownership and readiness interference.
Validation includes all four native helper suites, all 754 Swift tests, lint (17 existing warnings, no serious violations), helper compilation, and an independent autoreview. A new regression runs separate helper-host processes against an isolated container and uses the production Swift bridge client. It checks exclusive request claims, standby destructor behavior, readiness restoration, SIGKILL takeover, and graceful final cleanup.
For the before case, separate hosts running the main-branch watcher and cleanup functions reproduced both defects: the second host overwrote the first live host’s ready PID, then removed readiness on exit while the first host remained alive.
A separately compiled, Developer-ID-signed consumer also called the built IMsgCore library against those real helper processes:
The newly built, matching-Developer-ID-signed CLI successfully read one row from the real Messages database; private fields were withheld. These are process-lifecycle and protocol proofs, not Messages injection or delivery tests. SIP remains enabled, no private frameworks were loaded in the isolated hosts, and no messages were sent.
Upgrade boundary: old injected helpers do not acquire this lock. Stop the old injected Messages instance with
imsg launch --kill-onlybefore launching the updated helper. A patched helper cannot exclude an older helper that remains running. The docs now state that boundary explicitly.Release notes are prepared in the separate final notes PR to avoid sibling changelog conflicts. Credit: @omarshahine.