Skip to content

feat: Register Ruby with the BAML V1 bridge [sc-64730] - #4378

Merged
sxlijin merged 9 commits into
BoundaryML:canaryfrom
ryanmazzolini:feat/sc-64730/ruby-v1-bridge-loader
Aug 18, 2026
Merged

sxlijin merged 9 commits into
BoundaryML:canaryfrom
ryanmazzolini:feat/sc-64730/ruby-v1-bridge-loader

Conversation

@ryanmazzolini

@ryanmazzolini ryanmazzolini commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Description

Ruby needs a safe process-wide loader before the Ruby/Sorbet SDK can call the BAML 1.0 runtime.

This change registers Ruby as a permanent bridge language and adds the private Ruby FFI loader. The loader validates the V1 ABI, initializes exact compiled-program bytes once per process, and fails safely for incompatible runtimes and unsupported post-use forks.

Details

  • Assigns Ruby permanent bridge language ID 10.
  • Loads libbridge_cffi only through BAML_RUNTIME_PATH.
  • Validates the complete ABI revision 2 function table and runtime version.
  • Retains native libraries and callbacks for the process lifetime.
  • Handles concurrent initialization, retryable failures, terminal compatibility failures, and fork safety.
  • Adds Ruby version stamping and Rust/C/C++ ABI assertions.
  • Seeds sdk_test_ruby_sorbet in the existing Linux, macOS, and Windows SDK-test lanes without adding a Ruby-specific workflow or version matrix.

Shortcut: https://app.shortcut.com/odeko/story/64730

Testing

Passed locally on Apple Silicon macOS:

  • cargo nextest run -p bridge_cffi --all-features: 43 passed, 1 ignored
  • cargo nextest run -p sdk_test_ruby_sorbet --all-features
  • python3 -m unittest scripts.tests.test_baml_language_version: 13 passed
  • scripts/baml-language-version check
  • cargo fmt --all -- --check
  • actionlint .github/workflows/cargo-tests.reusable.yaml
  • shellcheck and bash -n for setup.sh
  • Ruby syntax checks for all new Ruby source and test files
  • Canonical, Go, and Swift CFFI headers are byte-identical

The full workspace regression suite was deferred to CI after local Rust build output grew to 33.6 GiB. Linux and Windows execution also remains for CI.

Developed with AI assistance.

Summary by CodeRabbit

  • New Features

    • Added an initial Ruby/Sorbet bridge SDK with runtime loading, program initialization, callbacks, error handling, and fork-safety protections.
    • Added Ruby bridge version tracking and compatibility validation.
    • Registered Ruby as a supported bridge language across SDK integrations.
  • Bug Fixes

    • Added validation for incompatible runtimes, invalid bytecode, malformed APIs, and unsafe buffer handling.
  • Tests

    • Added cross-platform Ruby/Sorbet coverage for loading, retries, concurrency, callbacks, native threads, and process lifecycle behavior.

Register Ruby as a permanent bridge language and add the private FFI loader with ABI validation, lifecycle safety, callback retention, fork protection, and exact-byte initialization.

Seed the Ruby/Sorbet harness in the existing cross-platform SDK-test matrix and extend version stamping and ABI assertions.
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

@ryanmazzolini is attempting to deploy a commit to the Boundary Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ed37138-a8cd-4643-b98b-595a1fd70878

📥 Commits

Reviewing files that changed from the base of the PR and between 6b755e5 and 03b61d8.

📒 Files selected for processing (3)
  • baml_language/sdk_tests/crates/ruby_sorbet/test/bridge_loader_test.rb
  • baml_language/sdk_tests/crates/ruby_sorbet/test/bridge_scenario.rb
  • baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/process_runtime.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/process_runtime.rb
  • baml_language/sdk_tests/crates/ruby_sorbet/test/bridge_loader_test.rb

📝 Walkthrough

Walkthrough

This PR adds a private Ruby bridge with CFFI-based runtime loading, Ruby bridge identity, version management, native fixtures, lifecycle tests, and Linux, macOS, and Windows CI coverage.

Changes

Ruby bridge contracts

Layer / File(s) Summary
Bridge identity and version contracts
baml_language/crates/bridge_cffi/..., baml_language/sdks/go/..., baml_language/sdks/swift/..., baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/version.rb, scripts/...
Ruby uses bridge-language value 10, Ruby metadata, SelectDefault union handling, and managed toolchain and runtime version constants.

Ruby runtime

Layer / File(s) Summary
Ruby FFI runtime and process lifecycle
baml_language/sdks/ruby/bridge_ruby/...
The Ruby bridge validates the native API, registers metadata and callbacks, initializes immutable program bytes, translates errors, queues callback events, and enforces program and fork rules.

Native fixtures and tests

Layer / File(s) Summary
Native fixtures and test setup
baml_language/sdk_tests/..., baml_language/sdk_tests/harness_setup/...
The test crate builds bridge and callback fixtures, generates ABI probe bytecode, installs Bundler dependencies, and exports paths through NEXTEST_ENV.
Ruby loader and lifecycle validation
baml_language/sdk_tests/crates/ruby_sorbet/test/..., baml_language/sdk_tests/crates/ruby_sorbet/src/lib.rs
Minitest scenarios cover retries, API validation, bytecode identity, concurrency, fork safety, callbacks, runtime initialization, and input validation.

CI wiring

Layer / File(s) Summary
Workspace and CI execution wiring
baml_language/Cargo.toml, baml_language/.config/nextest.toml, .github/workflows/...
The Ruby Sorbet crate is added to the workspace. Nextest setup runs on Unix and Windows. CI adds Linux, macOS, and Windows matrix entries and installs Ruby 3.2.2.

Estimated code review effort: 4 (Complex) | ~60 minutes

Mergeability Score: ⚪ Minimal · up to 03b61

The PR adds Ruby bridge registration and process-wide runtime loading with ABI, concurrency, and fork-safety handling; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant RubyTest
  participant BamlBridge
  participant ProcessRuntime
  participant NativeAPI
  RubyTest->>BamlBridge: initialize!(compiled_program_bytes)
  BamlBridge->>ProcessRuntime: initialize program
  ProcessRuntime->>NativeAPI: load and validate API table
  NativeAPI-->>ProcessRuntime: API and runtime status
  ProcessRuntime->>NativeAPI: register Ruby metadata and callback
  ProcessRuntime->>NativeAPI: initialize bytecode
  NativeAPI-->>RubyTest: success or bridge error
Loading

Possibly related PRs

  • BoundaryML/baml#4041: Provides the versioned CFFI bridge API foundation extended by Ruby.
  • BoundaryML/baml#4055: Establishes the CFFI bridge-language and ABI assertion conventions used for Ruby.
  • BoundaryML/baml#4315: Adds compatibility-validation and release-stamping infrastructure extended for Ruby.

Poem

A rabbit checks the bridge at night,
Ruby bytes hop through CFFI right.
Callbacks wait, then safely run,
Forks and bad paths come undone.
Across three systems tests take flight—
Sorbet loads, and all is bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: registering Ruby as a BAML V1 bridge language.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ryanmazzolini
ryanmazzolini marked this pull request as ready for review August 12, 2026 14:47

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67ce4bc2e6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"timeout": 30,
},
{
"sdk-label": "ruby-sorbet",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add Ruby's directory to the SDK coverage matrix

In the sdk-test-coverage job, covered directories are derived solely from each matrix entry's sdk-dir, but all three Ruby entries omit that property. Since this commit also adds baml_language/sdks/ruby, the coverage check will always include ruby in missingSdkDirs and call core.setFailed, breaking this workflow on every event; add "sdk-dir": "ruby" to each Ruby entry.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d0cf568: the matrix now declares Ruby’s SDK directory for Linux, macOS, and Windows. The coverage job can now account for baml_language/sdks/ruby.

Addressed with AI assistance.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
scripts/tests/test_baml_language_version.py (1)

251-254: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Verify both Ruby version constants in the stamp test.

make_fixture writes TOOLCHAIN_VERSION and BRIDGE_RUNTIME_VERSION. surface_versions() reads only TOOLCHAIN_VERSION. The nightly test also checks only that value. The later sync operation can overwrite a stale runtime version before check runs. Add BRIDGE_RUNTIME_VERSION to the extracted and asserted values.

Suggested test update
             "ruby": match(
                 "baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/version.rb",
                 r'^    TOOLCHAIN_VERSION = "([^"]+)"$',
             ),
+            "ruby_runtime": match(
+                "baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/version.rb",
+                r'^    BRIDGE_RUNTIME_VERSION = "([^"]+)"$',
+            ),
...
-        for sdk in ("node", "web", "rust", "go", "ruby", "csharp", "vsix"):
+        for sdk in (
+            "node",
+            "web",
+            "rust",
+            "go",
+            "ruby",
+            "ruby_runtime",
+            "csharp",
+            "vsix",
+        ):

Also applies to: 310-310

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/tests/test_baml_language_version.py` around lines 251 - 254, Update
the Ruby entry in the stamp test’s version extraction and assertion flow around
make_fixture, surface_versions(), and the nightly check to capture both
TOOLCHAIN_VERSION and BRIDGE_RUNTIME_VERSION from version.rb. Ensure the later
sync/check sequence validates each constant independently, so a stale
BRIDGE_RUNTIME_VERSION cannot be overwritten without detection.
baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/process_runtime.rb (1)

112-120: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Give the result queue and the callback error queue a drain path, or bound them.

receive_result appends to @result_events without a bound. pop_result_event is private and unused, so nothing drains it. The same applies to BorrowedBytesCallback#pop_error in native.rb at lines 245-249: load_api! retains the callback but never inspects its error queue. Once the native runtime delivers results, the process retains every event forever, and every exception raised inside the callback stays invisible.

This checkpoint does not call call_function, so no growth occurs yet. Add a short comment that records the intended consumer, or drain both queues, before result delivery lands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/process_runtime.rb`
around lines 112 - 120, Document the intended consumers for both unbounded
queues before result delivery is added: add a brief comment near
`receive_result`/`pop_result_event` identifying the future result-event
consumer, and near `BorrowedBytesCallback#pop_error` identifying how callback
errors will be drained or surfaced. Do not alter queue behavior or add unrelated
runtime handling.
baml_language/sdk_tests/crates/ruby_sorbet/setup.sh (1)

7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Both setup scripts assume the cargo artifact location. Each script hardcodes the workspace target directory and the debug profile, so BAML_RUBY_TEST_REAL_RUNTIME breaks when CARGO_TARGET_DIR is set or when the lane builds with --release.

  • baml_language/sdk_tests/crates/ruby_sorbet/setup.sh#L7-L7: set TARGET_DIR="${CARGO_TARGET_DIR:-$WORKSPACE_ROOT/target}", and derive the profile directory used at lines 25 and 30 instead of hardcoding debug.
  • baml_language/sdk_tests/crates/ruby_sorbet/setup.ps1#L4-L6: read $env:CARGO_TARGET_DIR when it is set, and derive the profile directory used at line 58 instead of hardcoding debug.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/sdk_tests/crates/ruby_sorbet/setup.sh` at line 7, Update both
ruby_sorbet setup scripts to honor Cargo’s configured artifact location and
build profile: in baml_language/sdk_tests/crates/ruby_sorbet/setup.sh lines 7,
25, and 30, use CARGO_TARGET_DIR when set and derive the profile directory
instead of assuming debug; in
baml_language/sdk_tests/crates/ruby_sorbet/setup.ps1 lines 4-6 and 58, use
$env:CARGO_TARGET_DIR when set and derive the corresponding profile directory
instead of hardcoding debug.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/cargo-tests.reusable.yaml:
- Around line 526-535: Add the sdk-dir field with value "ruby" to every Ruby
matrix entry, including the Linux entry at
.github/workflows/cargo-tests.reusable.yaml lines 526-535, the macOS entry at
lines 633-642, and the Windows entry at lines 745-754, so coverage discovery
includes the Ruby SDK directory.

In `@baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/native.rb`:
- Around line 111-124: Update read_table to check whether
find_function("baml_get_api_v1") returns nil before constructing FFI::Function,
and raise IncompatibleRuntimeError with the symbol-specific diagnostic used for
an unresolved baml_get_api_v1 entry. Keep the existing null-pointer validation
and FFI::NotFoundError handling unchanged.

---

Nitpick comments:
In `@baml_language/sdk_tests/crates/ruby_sorbet/setup.sh`:
- Line 7: Update both ruby_sorbet setup scripts to honor Cargo’s configured
artifact location and build profile: in
baml_language/sdk_tests/crates/ruby_sorbet/setup.sh lines 7, 25, and 30, use
CARGO_TARGET_DIR when set and derive the profile directory instead of assuming
debug; in baml_language/sdk_tests/crates/ruby_sorbet/setup.ps1 lines 4-6 and 58,
use $env:CARGO_TARGET_DIR when set and derive the corresponding profile
directory instead of hardcoding debug.

In `@baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/process_runtime.rb`:
- Around line 112-120: Document the intended consumers for both unbounded queues
before result delivery is added: add a brief comment near
`receive_result`/`pop_result_event` identifying the future result-event
consumer, and near `BorrowedBytesCallback#pop_error` identifying how callback
errors will be drained or surfaced. Do not alter queue behavior or add unrelated
runtime handling.

In `@scripts/tests/test_baml_language_version.py`:
- Around line 251-254: Update the Ruby entry in the stamp test’s version
extraction and assertion flow around make_fixture, surface_versions(), and the
nightly check to capture both TOOLCHAIN_VERSION and BRIDGE_RUNTIME_VERSION from
version.rb. Ensure the later sync/check sequence validates each constant
independently, so a stale BRIDGE_RUNTIME_VERSION cannot be overwritten without
detection.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e368d81-656e-421e-ae08-51e5230a8ab2

📥 Commits

Reviewing files that changed from the base of the PR and between 3085fcf and 67ce4bc.

⛔ Files ignored due to path filters (2)
  • baml_language/Cargo.lock is excluded by !**/*.lock
  • baml_language/sdk_tests/crates/ruby_sorbet/Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (30)
  • .github/workflows/cargo-tests.reusable.yaml
  • baml_language/.config/nextest.toml
  • baml_language/Cargo.toml
  • baml_language/crates/bridge_cffi/include/baml_cffi.h
  • baml_language/crates/bridge_cffi/src/ffi/runtime.rs
  • baml_language/crates/bridge_cffi/src/identity.rs
  • baml_language/crates/bridge_cffi/tests/abi_assertions.h
  • baml_language/crates/bridge_cffi/tests/abi_layout.rs
  • baml_language/sdk_tests/crates/ruby_sorbet/Cargo.toml
  • baml_language/sdk_tests/crates/ruby_sorbet/Gemfile
  • baml_language/sdk_tests/crates/ruby_sorbet/setup.ps1
  • baml_language/sdk_tests/crates/ruby_sorbet/setup.sh
  • baml_language/sdk_tests/crates/ruby_sorbet/src/lib.rs
  • baml_language/sdk_tests/crates/ruby_sorbet/test/bridge_loader_test.rb
  • baml_language/sdk_tests/crates/ruby_sorbet/test/bridge_scenario.rb
  • baml_language/sdk_tests/crates/ruby_sorbet/test/native/bridge_fixture.c
  • baml_language/sdk_tests/crates/ruby_sorbet/test/native/missing_getter.c
  • baml_language/sdk_tests/crates/ruby_sorbet/test/native/thread_callback.cpp
  • baml_language/sdk_tests/harness_setup/src/lib.rs
  • baml_language/sdks/go/baml_go/internal/cffi/include/baml_cffi.h
  • baml_language/sdks/ruby/bridge_ruby/Gemfile
  • baml_language/sdks/ruby/bridge_ruby/README.md
  • baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge.rb
  • baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/errors.rb
  • baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/native.rb
  • baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/process_runtime.rb
  • baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/version.rb
  • baml_language/sdks/swift/Sources/CBamlBridge/include/baml_cffi.h
  • scripts/baml-language-version
  • scripts/tests/test_baml_language_version.py

Comment thread .github/workflows/cargo-tests.reusable.yaml
Comment thread baml_language/sdks/ruby/bridge_ruby/lib/baml/bridge/native.rb
Ensure CI covers the Ruby SDK and missing native symbols fail with a stable diagnostic. Keep version stamps, callback ownership, target-directory handling, and Windows fixture compilation aligned across platforms.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0cf568338

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

candidate_path = nil
if @api.nil? && @terminal_error.nil?
candidate_path = configured_runtime_path!
claim_process!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear the process claim when opening the runtime fails

When BAML_RUNTIME_PATH names an existing but unloadable file, claim_process! records the current PID before load_api! raises the documented retryable RuntimeLoadError, and that claim is never cleared. If a prefork application subsequently forks and corrects the path in a worker, the worker receives ForkSafetyError even though no native library or runtime state was successfully loaded. Reset the ownership claim after an open failure, or otherwise distinguish failed pre-load attempts from native use.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9891fa9: a failed library open now releases the PID claim because no native state was created. The loader scenario now forks after that failure and proves the child can correct the path. The focused suite passes with 9 tests and 41 assertions.

Addressed with AI assistance.

Allow prefork workers to correct an unloadable runtime path because a failed library open creates no native state. Cover the retry in a forked child before the parent retries.
@rmazzolini

Copy link
Copy Markdown

Closed the three earlier summary-only review notes in d0cf568:

Focused validation passed, and CodeRabbit’s follow-up review approved the updated head.

Addressed with AI assistance.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9891fa9dc0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +129 to +131
# Opening a file does not create native state. Release the process
# claim so a prefork worker can correct the path and retry.
@owner_pid = nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve a concurrent initializer's process claim

When two threads enter while @api is unset, both can execute claim_process!; if the first thread's library open fails while the second is waiting with a valid candidate_path, this unconditional reset clears their shared PID claim. The second thread can then load and initialize the native runtime without restoring @owner_pid, so a later fork is incorrectly accepted and may use inherited native state. Clear the claim only when no other initialization attempt owns it, or re-claim inside the mutex before loading.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6b755e5: each thread now reclaims process ownership inside the serialized load section, after any earlier initializer has finished and possibly released a failed-open claim. The deterministic two-thread regression forces the reported ordering, verifies the surviving initializer owns the PID, and proves a later child is rejected. The focused suite passes with 9 tests and 42 assertions.

Addressed with AI assistance.

Reclaim process ownership inside the serialized load path when another initializer releases a failed-open claim. Add a deterministic concurrent regression that verifies the surviving initializer restores fork protection.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b755e54b6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

rescue LoadError => error
# Opening a file does not create native state. Release the process
# claim so a prefork worker can correct the path and retry.
@owner_pid = nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the fork claim until the initialization mutex unlocks

If another thread forks after this assignment but before the enclosing @mutex.synchronize unwinds, the child sees no owner at the initial fork check and then blocks forever on the mutex inherited from the vanished loader thread. The newly added re-claim at line 46 does not close this window because it runs only after acquiring that mutex; release the claim only once the mutex is unlocked, while ensuring a concurrent successful initializer cannot have its claim cleared.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 03b61d8: failed opens now keep the PID claim, while per-attempt tokens remain present until synchronize has released the mutex. A child can reclaim only after every attempt completed without creating native state.

The new regression forks while a failed open still holds the initialization section, verifies the child rejects promptly, then verifies recovery after the parent unlocks. This model also supersedes the earlier clear-and-reclaim fixes. The focused suite passes with 9 tests and 43 assertions.

Addressed with AI assistance.

Keep process ownership after an unloadable runtime and track active initialization attempts until their mutex is released. A forked child can reclaim only when the failed attempt completed without creating native state.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03b61d839e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +58 to +60
ensure
# Remove this token only after synchronize has released the mutex.
@initialization_attempts.delete(attempt) if attempt

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow fork recovery after the failed-open mutex unlocks

If another thread forks after @mutex.synchronize has unlocked but before this ensure deletes the attempt token, the child permanently inherits a nonempty @initialization_attempts array whose originating thread no longer exists. Although the library open failed and no native state exists, every later initialize! in that child therefore raises ForkSafetyError instead of permitting the documented corrected-path retry. The new token cleanup is fresh evidence of a post-unlock race distinct from the previously reported locked-mutex window; recovery should account for the mutex already being unlocked rather than relying solely on the inherited token being removed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a34fb72: fork recovery no longer depends on an inherited attempt token. The runtime now tracks only the active native library open. A child rejects while that native operation is active, then replaces the inherited Ruby mutex and retries once a failed open has returned without native state.

The regression now covers both sides of the boundary: it rejects a child while the open is active, then successfully retries from another child while the parent still holds the post-failure Ruby mutex. This removes the post-unlock token window entirely. The focused suite passes with 9 tests and 43 assertions.

Addressed with AI assistance.

Track the native library open directly instead of inferring it from initialization tokens. A child rejects during dlopen, then replaces the inherited Ruby mutex and retries once the failed open has returned without native state.
@sxlijin sxlijin changed the title [sc-64730] Register Ruby with the BAML V1 bridge feat: Register Ruby with the BAML V1 bridge [sc-64730] Aug 14, 2026
@sxlijin
sxlijin added this pull request to the merge queue Aug 14, 2026
@sxlijin

sxlijin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Sorry for the delay on this!

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 14, 2026
@sxlijin
sxlijin added this pull request to the merge queue Aug 17, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 17, 2026
@sxlijin
sxlijin added this pull request to the merge queue Aug 17, 2026
Merged via the queue into BoundaryML:canary with commit dc66c22 Aug 18, 2026
71 of 73 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.

3 participants