Skip to content

baml_language/feat: bridge_go#3354

Open
sxlijin wants to merge 1 commit intocanaryfrom
push-sszxsunturul
Open

baml_language/feat: bridge_go#3354
sxlijin wants to merge 1 commit intocanaryfrom
push-sszxsunturul

Conversation

@sxlijin
Copy link
Copy Markdown
Contributor

@sxlijin sxlijin commented Apr 10, 2026

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced Go language bindings for BAML runtime. Go developers can now initialize BAML environments, invoke BAML functions with typed arguments, handle typed errors, manage async callbacks, work with resource handles, retrieve runtime version information, and manage event lifecycle from Go applications.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Apr 10, 2026 8:40pm
promptfiddle Ready Ready Preview, Comment Apr 10, 2026 8:40pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new bridge_go crate providing FFI bindings to the BAML runtime. It includes Rust code compiled as a shared library (cdylib), Go code that dynamically loads and calls it via cgo, protobuf marshaling for inter-language data exchange, and comprehensive tests validating bidirectional callback and result handling.

Changes

Cohort / File(s) Summary
Rust Package Setup
baml_language/crates/bridge_go/Cargo.toml, baml_language/crates/bridge_go/build.sh
Defines bridge_go as a workspace Rust library compiled as cdylib with dependencies on internal bridge crates and async/protobuf libraries. Build script generates Go protobuf sources from CTypes proto definitions using protoc and protoc-gen-go.
Rust FFI Core
baml_language/crates/bridge_go/src/lib.rs, baml_language/crates/bridge_go/src/runtime.rs, baml_language/crates/bridge_go/src/functions.rs
Exports C-callable FFI functions: version() returns engine version, create_baml_runtime() initializes the BAML runtime from JSON-encoded source files, call_function() dispatches async function invocations with encoded protobuf arguments and returns acknowledgement, and memory/event helpers (free_buffer, flush_events).
Rust Support Modules
baml_language/crates/bridge_go/src/callbacks.rs, baml_language/crates/bridge_go/src/errors.rs, baml_language/crates/bridge_go/src/handle.rs, baml_language/crates/bridge_go/src/panic.rs
Implements callback registration and dispatch for bidirectional communication, error stringification with categorization (InvalidArgument, Client, Cancelled), handle cloning/release for reference management, and FFI-safe panic handling with unwinding recovery.
Go Module & FFI Bindings
baml_language/crates/bridge_go/go.mod, baml_language/crates/bridge_go/cffi/exports.go, baml_language/crates/bridge_go/cffi/lib.go, baml_language/crates/bridge_go/cffi/types.go
Defines Go module with protobuf dependency. cffi/lib.go provides low-level cgo bindings that dynamically load the shared library via dlopen/dlsym, resolve exported symbols, and expose functions like Init, FindLibrary, CallFunctionFromC, CreateBamlRuntime.
Go High-Level API
baml_language/crates/bridge_go/pkg/runtime.go, baml_language/crates/bridge_go/pkg/callbacks.go, baml_language/crates/bridge_go/pkg/errors.go, baml_language/crates/bridge_go/pkg/handle.go, baml_language/crates/bridge_go/pkg/init_callbacks.go, baml_language/crates/bridge_go/pkg/proto.go
Implements user-facing API: BamlRuntime wrapper with NewRuntime() and CallFunction(), channel-based callback handlers that map numeric IDs to Go channels, BAML error types with parsing, BamlHandle with lifecycle methods, protobuf encoding/decoding of function arguments and results, and callback registration via cgo-exported functions.
Tests
baml_language/crates/bridge_go/pkg/tests/engine_test.go, baml_language/crates/bridge_go/pkg/tests/call_function_test.go, baml_language/crates/bridge_go/pkg/tests/handle_test.go
Validates library initialization, runtime creation from inline BAML source, function dispatch with context cancellation, result conversion for primitives/enums/classes/unions/lists/maps, error handling for invalid functions and malformed BAML, and handle lifecycle operations.

Sequence Diagram(s)

sequenceDiagram
    participant Go as Go Client
    participant CFFI as CFFI Bindings<br/>(lib.go)
    participant Rust as Rust FFI<br/>(bridge_go lib)
    participant Engine as BAML Engine
    participant CB as Go Callback<br/>(callbacks.go)

    Go->>CFFI: CallFunction(name, args, id)
    CFFI->>Rust: call_function(name, args, id)
    
    activate Rust
    Rust->>Engine: runtime.call_function(name, kwargs)
    activate Engine
    Note over Engine: Async execution
    Engine-->>Rust: Result or Error
    deactivate Engine
    
    alt Success Path
        Rust->>CB: result_callback(id, is_done, data)
        CB->>CB: Decode & send to channel
    else Error Path
        Rust->>CB: error_callback(id, is_done, error_msg)
        CB->>CB: Parse error & send to channel
    end
    deactivate Rust
    
    CB-->>Go: Channel receives ResultCallback
    Go->>Go: Unblock CallFunction waiter
    Go-->>Go: Return result or error to caller
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 A bridge of bytes now spans the gap,
From Go to Rust, no overflow or trap,
With channels dancing, callbacks aligned,
The BAML runtime's yours to find!
Hopping between the FFI threads,
Where rabbits fear no panics or dreads. 🌉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.10% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'baml_language/feat: bridge_go' is specific to the main change—adding a new Go bridge FFI crate—but the path prefix is unnecessary and the colon-separated format suggests a conventional commit style without the full context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch push-sszxsunturul

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 and usage tips.

Copy link
Copy Markdown

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

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: 0a59b9eee6

ℹ️ 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 +55 to +57
case <-ctx.Done():
deleteCallback(callbackID)
return nil, ctx.Err()
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 Propagate context cancellation to Rust runtime

When ctx.Done() fires, this branch only removes the local callback entry and returns, but it never signals the Rust engine to cancel the in-flight call. That means timed-out/canceled requests keep running in the background (and can still consume LLM/runtime resources) even though the caller already received context.Canceled/DeadlineExceeded.

Useful? React with 👍 / 👎.

Comment on lines +144 to +145
default:
return nil, fmt.Errorf("unsupported outbound value type: %T", val.Value)
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 Decode handle-valued results in outboundToGo

outboundToGo falls through to an error for unhandled variants, and there is no case for BamlOutboundValue_HandleValue (nor checked/streaming wrappers). Rust can emit handle_value for opaque return values via in-process handle-table encoding, so valid function results are surfaced as decode failures instead of usable Go values.

Useful? React with 👍 / 👎.

Comment on lines +69 to +70
default:
return nil, fmt.Errorf("unsupported type: %T", v)
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 Support BamlHandle input encoding in goToInboundValue

The argument encoder rejects pkg.BamlHandle and returns unsupported type, so handles cannot be passed back into subsequent function calls. This breaks handle round-tripping even though the bridge exposes BamlHandle and the inbound protobuf schema includes a handle field.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (18)
baml_language/crates/bridge_go/pkg/tests/handle_test.go (1)

9-23: Consider adding one positive-path handle lifecycle test.

Current coverage validates invalid key behavior well; adding a runtime-backed valid handle case would also protect the successful Clone/Release path from regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/pkg/tests/handle_test.go` around lines 9 - 23,
Add a positive-path test that exercises the successful handle lifecycle: in the
same TestCloneAndReleaseHandle (or a new TestCloneAndReleaseHandle_Success)
obtain a runtime-backed valid pkg.BamlHandle (e.g., call the package factory or
function that returns a live handle instead of constructing Key:0), assert the
returned handle.Key is non-zero, call Clone() on that handle and assert
cloned.Key equals the original and is non-zero, then call Release() on both
original and clone and ensure no panics or errors; reference the pkg.BamlHandle
type and its Clone and Release methods when adding the assertions.
baml_language/crates/bridge_go/src/handle.rs (1)

3-11: Add Rust unit tests to lock these FFI contracts.

clone_handle returning 0 on miss and release_handle being safe on invalid keys are ABI-visible behaviors; please codify them with local #[cfg(test)] tests in this file.

Proposed test scaffold
+#[cfg(test)]
+mod tests {
+    use super::{clone_handle, release_handle};
+
+    #[test]
+    fn clone_invalid_handle_returns_zero() {
+        assert_eq!(clone_handle(0, 0), 0);
+    }
+
+    #[test]
+    fn release_invalid_handle_is_noop() {
+        release_handle(0, 0);
+    }
+}

As per coding guidelines, "**/*.rs: Prefer writing Rust unit tests over integration tests where possible" and "Always run cargo test --lib if you changed any Rust code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/src/handle.rs` around lines 3 - 11, Add Rust
unit tests in this file under a #[cfg(test)] mod to lock the FFI contracts:
write one test that calls clone_handle with a known-missing key and asserts it
returns 0 (use HANDLE_TABLE keys that are not inserted or a very large u64), and
write another test that calls release_handle with an invalid/nonexistent key and
verifies it does not panic (use std::panic::catch_unwind and assert the call
completes). Reference the exported functions clone_handle and release_handle and
the HANDLE_TABLE symbol in the tests so the ABI behaviors are codified.
baml_language/crates/bridge_go/go.mod (1)

5-5: Update protobuf version to align with repository baseline.

bridge_go/go.mod pins google.golang.org/protobuf to v1.33.0, while all other Go modules in the repository use v1.36.6. Both versions are compatible (same runtime API), so update to v1.36.6 to maintain consistency across the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/go.mod` at line 5, The protobuf dependency is
pinned to google.golang.org/protobuf v1.33.0; update that require line to use
v1.36.6 so it matches the repository baseline. Locate the require entry for
google.golang.org/protobuf (currently v1.33.0) in go.mod and change the version
to v1.36.6, then run go mod tidy to refresh go.sum and ensure consistency.
baml_language/crates/bridge_go/src/errors.rs (1)

3-60: Consider adding unit tests for error stringification.

This module converts BridgeError variants to categorized strings that the Go side parses via ParseBamlError. Unit tests would help ensure the prefix format (e.g., "BamlError: BamlInvalidArgumentError:") remains stable across refactors.

💡 Example test structure
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_not_initialized_error() {
        let err = BridgeError::NotInitialized;
        let s = bridge_error_to_string(&err);
        assert!(s.starts_with("BamlError: BamlInvalidArgumentError:"));
    }

    #[test]
    fn test_function_not_found_error() {
        let err = BridgeError::FunctionNotFound { name: "foo".into() };
        let s = bridge_error_to_string(&err);
        assert!(s.contains("Function not found: foo"));
    }
}

As per coding guidelines: "Prefer writing Rust unit tests over integration tests where possible".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/src/errors.rs` around lines 3 - 60, Add unit
tests in errors.rs to assert stable string prefixes and contents produced by
bridge_error_to_string and runtime_error_to_string: create tests for
BridgeError::NotInitialized, BridgeError::FunctionNotFound { name },
BridgeError::MissingArgument { function, parameter },
BridgeError::Internal(msg), BridgeError::Ctypes, and a RuntimeError case (e.g.,
RuntimeError::InvalidArgument and EngineError::Cancelled /
EngineError::FunctionNotFound) to ensure strings start with the expected
category tokens like "BamlError: BamlInvalidArgumentError:" or "BamlError:
BamlClientError:" and contain the specific details; call bridge_error_to_string
and runtime_error_to_string directly and use assertions like starts_with and
contains to lock the format.
baml_language/crates/bridge_go/src/runtime.rs (1)

30-32: Clarify the sentinel pointer usage.

std::ptr::dangling::<libc::c_void>() returns a well-aligned non-null pointer that doesn't point to valid memory. This works as a sentinel since the runtime is global, but a brief doc comment or using a named constant would improve clarity for future maintainers.

📝 Suggested documentation
+        // Return a non-null sentinel pointer. The actual runtime is stored globally
+        // in bridge_cffi::engine, so this pointer is never dereferenced.
         Ok(std::ptr::dangling::<libc::c_void>())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/src/runtime.rs` around lines 30 - 32, The use
of std::ptr::dangling::<libc::c_void>() as a sentinel pointer in runtime.rs is
unclear to future readers; replace the inline call with a clearly named sentinel
(e.g., RUNTIME_SENTINEL) or add a short doc comment on the function that returns
it explaining that it is a well-aligned, non-null sentinel used because the
runtime is global; update the return site where
std::ptr::dangling::<libc::c_void>() is currently used to reference the named
constant or add the doc comment above the function so maintainers see the intent
immediately.
baml_language/crates/bridge_go/pkg/tests/call_function_test.go (2)

64-77: Shared mutable state without synchronization.

testRT is a package-level variable accessed by multiple test functions. While Go's test runner executes tests sequentially by default, if t.Parallel() is ever added, this will race. Consider using sync.Once for thread-safe lazy initialization.

💡 Optional: Use sync.Once for safety
+var testRTOnce sync.Once
+var testRTErr error
+
 func getTestRuntime(t *testing.T) *pkg.BamlRuntime {
 	t.Helper()
-	if testRT != nil {
-		return testRT
-	}
-	rt, err := pkg.NewRuntime(".", map[string]string{"main.baml": callFunctionBamlSource})
-	if err != nil {
-		t.Fatalf("NewRuntime failed: %v", err)
-	}
-	testRT = rt
+	testRTOnce.Do(func() {
+		testRT, testRTErr = pkg.NewRuntime(".", map[string]string{"main.baml": callFunctionBamlSource})
+	})
+	if testRTErr != nil {
+		t.Fatalf("NewRuntime failed: %v", testRTErr)
+	}
 	return testRT
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/pkg/tests/call_function_test.go` around lines
64 - 77, The package-level variable testRT is lazily initialized in
getTestRuntime and can race if tests run in parallel; replace the ad-hoc nil
check with a sync.Once-based initialization (declare a once variable and use
once.Do to call the pkg.NewRuntime creation path) so getTestRuntime uses once.Do
to create testRT (and capture/handle any error inside the once body or propagate
it safely), referencing testRT, getTestRuntime, and pkg.NewRuntime to locate
where to apply the change.

135-142: Unchecked type assertions may panic.

Lines 135 and 139 use direct type assertions (result.(map[string]any)) without checking the ok boolean, unlike other tests in this file (e.g., lines 118-121). A failed assertion will panic rather than produce a clear test failure.

♻️ Use checked type assertions for consistency
-	m := result.(map[string]any)
+	m, ok := result.(map[string]any)
+	if !ok {
+		t.Fatalf("expected map, got %T", result)
+	}
 	if m["name"] != "Bob" {
 		t.Fatalf("expected name=Bob, got %v", m["name"])
 	}
-	addr := m["address"].(map[string]any)
+	addr, ok := m["address"].(map[string]any)
+	if !ok {
+		t.Fatalf("expected address to be map, got %T", m["address"])
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/pkg/tests/call_function_test.go` around lines
135 - 142, Replace the unchecked type assertions that can panic—change
result.(map[string]any) to a comma-ok checked assertion (e.g., m, ok :=
result.(map[string]any); if !ok { t.Fatalf("expected result to be
map[string]any, got %T", result) }) and likewise change addr :=
m["address"].(map[string]any) to a checked form (addr, ok :=
m["address"].(map[string]any); if !ok { t.Fatalf("expected m[\"address\"] to be
map[string]any, got %T", m["address"]) }), and also validate the types of
m["name"] and addr["zip"] before comparing values to avoid panics.
baml_language/crates/bridge_go/cffi/lib.go (4)

216-219: Slice-to-pointer conversion is safe but fragile.

Taking &encodedArgs[0] is safe here because len(encodedArgs) > 0 is checked first. This is a common Go/cgo pattern. Consider adding a comment for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/cffi/lib.go` around lines 216 - 219, The
slice-to-pointer conversion for encodedArgs into cArgs is safe due to the
preceding len(encodedArgs) > 0 check but is fragile; add an inline comment next
to the conversion (where encodedArgs and cArgs are used in lib.go) explaining
that the len check guarantees &encodedArgs[0] is valid for cgo, e.g., "safe: len
checked above" and optionally mention why we don't use nil for empty slices, so
future maintainers don't remove the check or refactor incorrectly.

183-194: CreateBamlRuntime returns nil error message on failure.

When wrapCreateBamlRuntime returns nil, the error message is generic: "create_baml_runtime failed (returned null)". The Rust side may have set a more descriptive error via a different mechanism. Consider whether the Rust FFI should return an error buffer instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/cffi/lib.go` around lines 183 - 194,
CreateBamlRuntime currently reports a generic error when C.wrapCreateBamlRuntime
returns nil; update the FFI to surface more descriptive Rust-side errors by
either (a) modifying the Rust API to return an error buffer/string (and exposing
a new C wrapper like wrapGetLastError or returning a struct with ptr+err), or
(b) adding an exported function (e.g., wrap_baml_last_error) that retrieves the
last error message set by Rust; then change CreateBamlRuntime to call that
function when ptr == nil and include the returned error text in the fmt.Errorf
message instead of the generic "create_baml_runtime failed (returned null)".

142-167: FindLibrary assumes standard cargo output paths.

The function only checks target/debug and target/release. Custom cargo profiles or CARGO_TARGET_DIR overrides will cause the library to not be found. This is acceptable for development/testing but should be documented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/cffi/lib.go` around lines 142 - 167,
FindLibrary currently only checks target/debug and target/release which breaks
when CARGO_TARGET_DIR or custom cargo profiles are used; update FindLibrary to
also consult the CARGO_TARGET_DIR environment variable (if set) and add
candidates under that directory (both debug/release or the profile dir), and
additionally walk upward from crateDir to discover any "target" directories
(e.g., search parent dirs for a "target" folder and add debug/release candidates
found there); ensure the returned error includes the expanded absolute paths
that were attempted for easier debugging. Use the function name FindLibrary and
variables candidates/crateDir when implementing these checks.

129-139: dlsym returning nil may not indicate an error.

Per POSIX, dlsym can legitimately return NULL if the symbol's value is NULL. The check on line 134-137 would incorrectly treat this as an error. However, for function pointers in a Rust cdylib, this is practically impossible, so this is acceptable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/cffi/lib.go` around lines 129 - 139, The
current resolveSymbol treats any nil from C.dlsym as an error; instead, call
C.dlerror() after dlsym and only treat it as an error if dlerror returns
non-NULL. In resolveSymbol, keep the initial C.dlerror() to clear errors, call
sym := C.dlsym(handle, cName), then check errPtr := C.dlerror(); if errPtr !=
nil convert it (C.GoString) and return an error, otherwise return sym, nil
(allowing a legitimate NULL symbol value without error).
baml_language/crates/bridge_go/pkg/callbacks.go (3)

44-52: Silent panic recovery may obscure bugs.

safeSend and safeClose silently swallow panics without any logging. While this prevents crashes when sending to a closed channel, it could mask programming errors during development. Consider adding debug logging.

💡 Optional: Add debug logging
 func safeSend(ch chan ResultCallback, res ResultCallback) {
-	defer func() { recover() }()
+	defer func() {
+		if r := recover(); r != nil {
+			// Channel was closed; callback already handled
+		}
+	}()
 	ch <- res
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/pkg/callbacks.go` around lines 44 - 52,
safeSend and safeClose currently swallow panics silently via recover(), which
can hide bugs; update both functions (safeSend, safeClose) to check the
recovered value and, when non-nil, emit a debug-level log message including the
recovered value and a short context string (e.g., "panic sending to channel" /
"panic closing channel") so developers can see the issue in logs; use the
existing project logger if available or the standard log package, and keep the
recover to avoid crashes while surfacing the error for debugging.

76-90: Unused isDone parameter in error_callback.

The isDone parameter is declared but never used—the function always closes the channel regardless. This is fine if intentional (errors are always terminal), but the parameter could be removed from the Go signature if Rust always passes 1.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/pkg/callbacks.go` around lines 76 - 90, The
exported function error_callback currently ignores the isDone parameter; update
it so the handler respects isDone: read isDone (e.g., if isDone != 0) then send
the error and close the callback channel and deleteCallback(uint32(id)),
otherwise only send the error on cb.channel without closing or deleting so
non-terminal errors are handled; if instead Rust truly always passes terminal
errors, remove the isDone parameter from error_callback's signature and adjust
any corresponding C/Rust callers to stop passing it. Ensure you reference the
dynamicCallbacks lookup, cb.channel usage, safeClose(cb.channel) and
deleteCallback(uint32(id)) when making the change.

29-36: Potential ID wraparound after ~4 billion calls.

The nextCallbackID counter will wrap around to 0 after math.MaxUint32 calls. If old callbacks are still pending when this happens, ID collisions could cause incorrect routing. This is unlikely in practice but worth documenting or handling defensively.

💡 Optional: Add collision check
 func createUniqueID() (uint32, chan ResultCallback) {
 	id := nextCallbackID.Add(1)
+	// Note: ID 0 is reserved/skipped after wraparound
+	if id == 0 {
+		id = nextCallbackID.Add(1)
+	}
 	ch := make(chan ResultCallback, 64)
 	callbackMutex.Lock()
+	if _, exists := dynamicCallbacks[id]; exists {
+		// Extremely unlikely collision after wraparound
+		callbackMutex.Unlock()
+		return createUniqueID() // retry
+	}
 	dynamicCallbacks[id] = callbackData{channel: ch}
 	callbackMutex.Unlock()
 	return id, ch
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/pkg/callbacks.go` around lines 29 - 36, The
createUniqueID function can produce colliding IDs after nextCallbackID wraps;
change it to loop until a free id is found by repeatedly incrementing
nextCallbackID and checking dynamicCallbacks under callbackMutex before
inserting so you never overwrite an existing entry. Specifically, inside
createUniqueID use a loop that calls nextCallbackID.Add(1), acquires
callbackMutex, checks if dynamicCallbacks contains that id, and only then
creates/assigns the channel and returns the id and channel (ensure the channel
is created only when a free id is confirmed); reference createUniqueID,
nextCallbackID, dynamicCallbacks, and callbackMutex when making the change.
baml_language/crates/bridge_go/pkg/runtime.go (2)

49-58: Context cancellation only abandons the Go-side wait, not the Rust task.

When ctx.Done() fires, the callback is deleted and ctx.Err() is returned, but the spawned Rust task continues executing. This is acceptable for short-lived operations, but for long-running LLM calls, the Rust side will complete and attempt to send to a deleted callback (which is handled gracefully). Consider documenting this behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/pkg/runtime.go` around lines 49 - 58, The
select that waits on ch vs ctx.Done() (using ch, callbackID, deleteCallback,
ctx.Done(), result.Error/result.Data) only aborts the Go-side wait and deletes
the callback; the spawned Rust task continues and may later attempt to send to
the deleted callback (which the Rust side already handles). Update the
function's documentation/comments to explicitly state this behavior and tradeoff
(Go cancels local wait and removes callback but does not cancel the Rust task),
and mention that if true cancellation is required the Rust side must be taught
to accept/observe cancellation tokens or an explicit cancellation API should be
added; reference ch, callbackID and deleteCallback in the doc so callers know
why the Rust task continues after ctx cancellation.

13-15: ptr field is stored but never used.

The BamlRuntime.ptr field is assigned during NewRuntime but never referenced in CallFunction or elsewhere. Per the AI summary, the runtime is global, so this pointer is effectively a sentinel. Consider documenting this or removing the field if it serves no purpose.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/pkg/runtime.go` around lines 13 - 15, The
BamlRuntime.ptr field is unused—remove the sentinel field and any assignments to
it in NewRuntime, and update related code (BamlRuntime struct definition and
NewRuntime) so callers and CallFunction use the remaining fields/methods without
relying on ptr; alternatively, if you want to preserve a sentinel, add a clear
comment above the ptr field explaining its purpose and ensure
NewRuntime/CallFunction reference it (or a no-op cast) so it's intentionally
documented rather than left unused.
baml_language/crates/bridge_go/src/callbacks.rs (1)

11-18: Silent failure on re-registration could mask initialization bugs.

OnceCell::set() silently discards the error when callbacks are already registered. While idempotent behavior is intentional, a debug log or assert in debug builds could help catch unintended double-initialization.

💡 Optional: Log on re-registration attempt
 pub extern "C" fn register_callbacks(
     result_callback_fn: CallbackFn,
     error_callback_fn: CallbackFn,
 ) {
-    let _ = RESULT_CALLBACK_FN.set(result_callback_fn);
-    let _ = ERROR_CALLBACK_FN.set(error_callback_fn);
+    if RESULT_CALLBACK_FN.set(result_callback_fn).is_err() {
+        #[cfg(debug_assertions)]
+        eprintln!("bridge_go: result callback already registered");
+    }
+    if ERROR_CALLBACK_FN.set(error_callback_fn).is_err() {
+        #[cfg(debug_assertions)]
+        eprintln!("bridge_go: error callback already registered");
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/src/callbacks.rs` around lines 11 - 18, The
register_callbacks function currently ignores OnceCell::set() errors which
silently hides double-registration; update register_callbacks to check the
Result returned by RESULT_CALLBACK_FN.set(...) and ERROR_CALLBACK_FN.set(...),
and when set returns Err (meaning already initialized) either emit a debug-only
assertion (e.g., debug_assert!(false, "register_callbacks called twice for
RESULT_CALLBACK_FN")) or write a debug log (e.g., eprintln! or crate logger)
that includes the symbol name and the existing/attempted state so
double-initialization is visible during development; keep the public API
unchanged but ensure the check references register_callbacks,
RESULT_CALLBACK_FN, ERROR_CALLBACK_FN and OnceCell::set to locate the code.
baml_language/crates/bridge_go/src/functions.rs (1)

1-98: Consider adding unit tests for this module.

As per coding guidelines, prefer writing Rust unit tests where possible. This module has testable logic (argument decoding, error encoding) that could benefit from unit tests.

Based on learnings: "Prefer writing Rust unit tests over integration tests where possible"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/src/functions.rs` around lines 1 - 98, Add
Rust unit tests for this module by creating a #[cfg(test)] mod tests that
exercises the pure/testable parts: 1) test call_function_inner returns
Err(BridgeError::NullFunctionName) when function_name is null, 2) test decoding
of CallFunctionArgs from a known buffer and conversion via kwargs_to_bex_values
to ensure argument decoding succeeds, and 3) test encode_success() and
encode_error() produce CallAck protobufs with expected response variants (decode
the Buffer back to CallAck and assert the response). Use the existing symbols
call_function_inner, CallFunctionArgs, kwargs_to_bex_values, encode_success, and
encode_error and mock or construct minimal inputs (e.g., empty/default
CallFunctionArgs) so tests do not require the async runtime or external
services. Ensure tests live in the same file under a tests module and assert
expected Result or decoded protobuf values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@baml_language/crates/bridge_go/build.sh`:
- Around line 11-15: The paired export+command-substitution in build.sh masks
command failures (SC2155); split the assignment and export so failures surface:
assign the dirname result to a local variable (e.g., protoc_dir or mise_dir)
using command substitution (for both the PROTOC_GEN_GO_PATH branch and the mise
branch that calls `mise which protoc-gen-go`), check the command succeeded if
needed, then export PATH="$protoc_dir:$PATH" (or export PATH="$mise_dir:$PATH");
update the two blocks around the export PATH lines to use these separate
assignment-then-export steps so non-zero exits are not masked.

In `@baml_language/crates/bridge_go/pkg/tests/call_function_test.go`:
- Around line 107-109: The failure message is inconsistent with the asserted
value: the test asserts that result == "red" but t.Fatalf says "expected 'Red'";
update the t.Fatalf call that references result to match the asserted lowercase
value (e.g., "expected 'red', got %v") so the error message accurately reflects
the expectation; locate the t.Fatalf call in call_function_test.go that checks
the variable result and adjust its message accordingly.

In `@baml_language/crates/bridge_go/src/runtime.rs`:
- Around line 16-22: Add explicit null-pointer checks for the incoming C
pointers before calling CStr::from_ptr: inside the ffi_safe_ptr closure, test if
root_path.is_null() and src_files_json.is_null() and return an Err with a
descriptive message (e.g., format!("Null pointer for root_path") / format!("Null
pointer for src_files_json")) instead of calling unsafe CStr::from_ptr; then
proceed to call unsafe CStr::from_ptr(...) only after each pointer is confirmed
non-null. Ensure you reference the existing symbols ffi_safe_ptr, root_path,
src_files_json and CStr::from_ptr when making the changes so the early-return
error integrates with the surrounding Result flow.

---

Nitpick comments:
In `@baml_language/crates/bridge_go/cffi/lib.go`:
- Around line 216-219: The slice-to-pointer conversion for encodedArgs into
cArgs is safe due to the preceding len(encodedArgs) > 0 check but is fragile;
add an inline comment next to the conversion (where encodedArgs and cArgs are
used in lib.go) explaining that the len check guarantees &encodedArgs[0] is
valid for cgo, e.g., "safe: len checked above" and optionally mention why we
don't use nil for empty slices, so future maintainers don't remove the check or
refactor incorrectly.
- Around line 183-194: CreateBamlRuntime currently reports a generic error when
C.wrapCreateBamlRuntime returns nil; update the FFI to surface more descriptive
Rust-side errors by either (a) modifying the Rust API to return an error
buffer/string (and exposing a new C wrapper like wrapGetLastError or returning a
struct with ptr+err), or (b) adding an exported function (e.g.,
wrap_baml_last_error) that retrieves the last error message set by Rust; then
change CreateBamlRuntime to call that function when ptr == nil and include the
returned error text in the fmt.Errorf message instead of the generic
"create_baml_runtime failed (returned null)".
- Around line 142-167: FindLibrary currently only checks target/debug and
target/release which breaks when CARGO_TARGET_DIR or custom cargo profiles are
used; update FindLibrary to also consult the CARGO_TARGET_DIR environment
variable (if set) and add candidates under that directory (both debug/release or
the profile dir), and additionally walk upward from crateDir to discover any
"target" directories (e.g., search parent dirs for a "target" folder and add
debug/release candidates found there); ensure the returned error includes the
expanded absolute paths that were attempted for easier debugging. Use the
function name FindLibrary and variables candidates/crateDir when implementing
these checks.
- Around line 129-139: The current resolveSymbol treats any nil from C.dlsym as
an error; instead, call C.dlerror() after dlsym and only treat it as an error if
dlerror returns non-NULL. In resolveSymbol, keep the initial C.dlerror() to
clear errors, call sym := C.dlsym(handle, cName), then check errPtr :=
C.dlerror(); if errPtr != nil convert it (C.GoString) and return an error,
otherwise return sym, nil (allowing a legitimate NULL symbol value without
error).

In `@baml_language/crates/bridge_go/go.mod`:
- Line 5: The protobuf dependency is pinned to google.golang.org/protobuf
v1.33.0; update that require line to use v1.36.6 so it matches the repository
baseline. Locate the require entry for google.golang.org/protobuf (currently
v1.33.0) in go.mod and change the version to v1.36.6, then run go mod tidy to
refresh go.sum and ensure consistency.

In `@baml_language/crates/bridge_go/pkg/callbacks.go`:
- Around line 44-52: safeSend and safeClose currently swallow panics silently
via recover(), which can hide bugs; update both functions (safeSend, safeClose)
to check the recovered value and, when non-nil, emit a debug-level log message
including the recovered value and a short context string (e.g., "panic sending
to channel" / "panic closing channel") so developers can see the issue in logs;
use the existing project logger if available or the standard log package, and
keep the recover to avoid crashes while surfacing the error for debugging.
- Around line 76-90: The exported function error_callback currently ignores the
isDone parameter; update it so the handler respects isDone: read isDone (e.g.,
if isDone != 0) then send the error and close the callback channel and
deleteCallback(uint32(id)), otherwise only send the error on cb.channel without
closing or deleting so non-terminal errors are handled; if instead Rust truly
always passes terminal errors, remove the isDone parameter from error_callback's
signature and adjust any corresponding C/Rust callers to stop passing it. Ensure
you reference the dynamicCallbacks lookup, cb.channel usage,
safeClose(cb.channel) and deleteCallback(uint32(id)) when making the change.
- Around line 29-36: The createUniqueID function can produce colliding IDs after
nextCallbackID wraps; change it to loop until a free id is found by repeatedly
incrementing nextCallbackID and checking dynamicCallbacks under callbackMutex
before inserting so you never overwrite an existing entry. Specifically, inside
createUniqueID use a loop that calls nextCallbackID.Add(1), acquires
callbackMutex, checks if dynamicCallbacks contains that id, and only then
creates/assigns the channel and returns the id and channel (ensure the channel
is created only when a free id is confirmed); reference createUniqueID,
nextCallbackID, dynamicCallbacks, and callbackMutex when making the change.

In `@baml_language/crates/bridge_go/pkg/runtime.go`:
- Around line 49-58: The select that waits on ch vs ctx.Done() (using ch,
callbackID, deleteCallback, ctx.Done(), result.Error/result.Data) only aborts
the Go-side wait and deletes the callback; the spawned Rust task continues and
may later attempt to send to the deleted callback (which the Rust side already
handles). Update the function's documentation/comments to explicitly state this
behavior and tradeoff (Go cancels local wait and removes callback but does not
cancel the Rust task), and mention that if true cancellation is required the
Rust side must be taught to accept/observe cancellation tokens or an explicit
cancellation API should be added; reference ch, callbackID and deleteCallback in
the doc so callers know why the Rust task continues after ctx cancellation.
- Around line 13-15: The BamlRuntime.ptr field is unused—remove the sentinel
field and any assignments to it in NewRuntime, and update related code
(BamlRuntime struct definition and NewRuntime) so callers and CallFunction use
the remaining fields/methods without relying on ptr; alternatively, if you want
to preserve a sentinel, add a clear comment above the ptr field explaining its
purpose and ensure NewRuntime/CallFunction reference it (or a no-op cast) so
it's intentionally documented rather than left unused.

In `@baml_language/crates/bridge_go/pkg/tests/call_function_test.go`:
- Around line 64-77: The package-level variable testRT is lazily initialized in
getTestRuntime and can race if tests run in parallel; replace the ad-hoc nil
check with a sync.Once-based initialization (declare a once variable and use
once.Do to call the pkg.NewRuntime creation path) so getTestRuntime uses once.Do
to create testRT (and capture/handle any error inside the once body or propagate
it safely), referencing testRT, getTestRuntime, and pkg.NewRuntime to locate
where to apply the change.
- Around line 135-142: Replace the unchecked type assertions that can
panic—change result.(map[string]any) to a comma-ok checked assertion (e.g., m,
ok := result.(map[string]any); if !ok { t.Fatalf("expected result to be
map[string]any, got %T", result) }) and likewise change addr :=
m["address"].(map[string]any) to a checked form (addr, ok :=
m["address"].(map[string]any); if !ok { t.Fatalf("expected m[\"address\"] to be
map[string]any, got %T", m["address"]) }), and also validate the types of
m["name"] and addr["zip"] before comparing values to avoid panics.

In `@baml_language/crates/bridge_go/pkg/tests/handle_test.go`:
- Around line 9-23: Add a positive-path test that exercises the successful
handle lifecycle: in the same TestCloneAndReleaseHandle (or a new
TestCloneAndReleaseHandle_Success) obtain a runtime-backed valid pkg.BamlHandle
(e.g., call the package factory or function that returns a live handle instead
of constructing Key:0), assert the returned handle.Key is non-zero, call Clone()
on that handle and assert cloned.Key equals the original and is non-zero, then
call Release() on both original and clone and ensure no panics or errors;
reference the pkg.BamlHandle type and its Clone and Release methods when adding
the assertions.

In `@baml_language/crates/bridge_go/src/callbacks.rs`:
- Around line 11-18: The register_callbacks function currently ignores
OnceCell::set() errors which silently hides double-registration; update
register_callbacks to check the Result returned by RESULT_CALLBACK_FN.set(...)
and ERROR_CALLBACK_FN.set(...), and when set returns Err (meaning already
initialized) either emit a debug-only assertion (e.g., debug_assert!(false,
"register_callbacks called twice for RESULT_CALLBACK_FN")) or write a debug log
(e.g., eprintln! or crate logger) that includes the symbol name and the
existing/attempted state so double-initialization is visible during development;
keep the public API unchanged but ensure the check references
register_callbacks, RESULT_CALLBACK_FN, ERROR_CALLBACK_FN and OnceCell::set to
locate the code.

In `@baml_language/crates/bridge_go/src/errors.rs`:
- Around line 3-60: Add unit tests in errors.rs to assert stable string prefixes
and contents produced by bridge_error_to_string and runtime_error_to_string:
create tests for BridgeError::NotInitialized, BridgeError::FunctionNotFound {
name }, BridgeError::MissingArgument { function, parameter },
BridgeError::Internal(msg), BridgeError::Ctypes, and a RuntimeError case (e.g.,
RuntimeError::InvalidArgument and EngineError::Cancelled /
EngineError::FunctionNotFound) to ensure strings start with the expected
category tokens like "BamlError: BamlInvalidArgumentError:" or "BamlError:
BamlClientError:" and contain the specific details; call bridge_error_to_string
and runtime_error_to_string directly and use assertions like starts_with and
contains to lock the format.

In `@baml_language/crates/bridge_go/src/functions.rs`:
- Around line 1-98: Add Rust unit tests for this module by creating a
#[cfg(test)] mod tests that exercises the pure/testable parts: 1) test
call_function_inner returns Err(BridgeError::NullFunctionName) when
function_name is null, 2) test decoding of CallFunctionArgs from a known buffer
and conversion via kwargs_to_bex_values to ensure argument decoding succeeds,
and 3) test encode_success() and encode_error() produce CallAck protobufs with
expected response variants (decode the Buffer back to CallAck and assert the
response). Use the existing symbols call_function_inner, CallFunctionArgs,
kwargs_to_bex_values, encode_success, and encode_error and mock or construct
minimal inputs (e.g., empty/default CallFunctionArgs) so tests do not require
the async runtime or external services. Ensure tests live in the same file under
a tests module and assert expected Result or decoded protobuf values.

In `@baml_language/crates/bridge_go/src/handle.rs`:
- Around line 3-11: Add Rust unit tests in this file under a #[cfg(test)] mod to
lock the FFI contracts: write one test that calls clone_handle with a
known-missing key and asserts it returns 0 (use HANDLE_TABLE keys that are not
inserted or a very large u64), and write another test that calls release_handle
with an invalid/nonexistent key and verifies it does not panic (use
std::panic::catch_unwind and assert the call completes). Reference the exported
functions clone_handle and release_handle and the HANDLE_TABLE symbol in the
tests so the ABI behaviors are codified.

In `@baml_language/crates/bridge_go/src/runtime.rs`:
- Around line 30-32: The use of std::ptr::dangling::<libc::c_void>() as a
sentinel pointer in runtime.rs is unclear to future readers; replace the inline
call with a clearly named sentinel (e.g., RUNTIME_SENTINEL) or add a short doc
comment on the function that returns it explaining that it is a well-aligned,
non-null sentinel used because the runtime is global; update the return site
where std::ptr::dangling::<libc::c_void>() is currently used to reference the
named constant or add the doc comment above the function so maintainers see the
intent immediately.
🪄 Autofix (Beta)

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

Run ID: a3403ebb-8861-497e-a2d6-1a5af71af581

📥 Commits

Reviewing files that changed from the base of the PR and between d124833 and 0a59b9e.

⛔ Files ignored due to path filters (4)
  • baml_language/Cargo.lock is excluded by !**/*.lock
  • baml_language/crates/bridge_go/cffi/proto/baml/cffi/v1/baml_inbound.pb.go is excluded by !**/*.pb.go
  • baml_language/crates/bridge_go/cffi/proto/baml/cffi/v1/baml_outbound.pb.go is excluded by !**/*.pb.go
  • baml_language/crates/bridge_go/go.sum is excluded by !**/*.sum
📒 Files selected for processing (22)
  • baml_language/crates/bridge_go/Cargo.toml
  • baml_language/crates/bridge_go/build.sh
  • baml_language/crates/bridge_go/cffi/exports.go
  • baml_language/crates/bridge_go/cffi/lib.go
  • baml_language/crates/bridge_go/cffi/types.go
  • baml_language/crates/bridge_go/go.mod
  • baml_language/crates/bridge_go/pkg/callbacks.go
  • baml_language/crates/bridge_go/pkg/errors.go
  • baml_language/crates/bridge_go/pkg/handle.go
  • baml_language/crates/bridge_go/pkg/init_callbacks.go
  • baml_language/crates/bridge_go/pkg/proto.go
  • baml_language/crates/bridge_go/pkg/runtime.go
  • baml_language/crates/bridge_go/pkg/tests/call_function_test.go
  • baml_language/crates/bridge_go/pkg/tests/engine_test.go
  • baml_language/crates/bridge_go/pkg/tests/handle_test.go
  • baml_language/crates/bridge_go/src/callbacks.rs
  • baml_language/crates/bridge_go/src/errors.rs
  • baml_language/crates/bridge_go/src/functions.rs
  • baml_language/crates/bridge_go/src/handle.rs
  • baml_language/crates/bridge_go/src/lib.rs
  • baml_language/crates/bridge_go/src/panic.rs
  • baml_language/crates/bridge_go/src/runtime.rs

Comment on lines +11 to +15
if [ -n "${PROTOC_GEN_GO_PATH:-}" ]; then
export PATH="$(dirname "${PROTOC_GEN_GO_PATH}"):${PATH}"
elif command -v mise &>/dev/null && mise which protoc-gen-go &>/dev/null; then
export PATH="$(dirname "$(mise which protoc-gen-go)")":${PATH}
fi
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.

⚠️ Potential issue | 🟡 Minor

Declare and assign separately to avoid masking return values.

Shellcheck SC2155: When export and command substitution are combined, a non-zero exit from the subshell is masked by export's success.

🔧 Proposed fix
 # Resolve protoc-gen-go
 if [ -n "${PROTOC_GEN_GO_PATH:-}" ]; then
-    export PATH="$(dirname "${PROTOC_GEN_GO_PATH}"):${PATH}"
+    protoc_gen_go_dir="$(dirname "${PROTOC_GEN_GO_PATH}")"
+    export PATH="${protoc_gen_go_dir}:${PATH}"
 elif command -v mise &>/dev/null && mise which protoc-gen-go &>/dev/null; then
-    export PATH="$(dirname "$(mise which protoc-gen-go)")":${PATH}
+    mise_protoc_path="$(mise which protoc-gen-go)"
+    export PATH="$(dirname "${mise_protoc_path}"):${PATH}"
 fi
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 12-12: Declare and assign separately to avoid masking return values.

(SC2155)


[warning] 14-14: Declare and assign separately to avoid masking return values.

(SC2155)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/build.sh` around lines 11 - 15, The paired
export+command-substitution in build.sh masks command failures (SC2155); split
the assignment and export so failures surface: assign the dirname result to a
local variable (e.g., protoc_dir or mise_dir) using command substitution (for
both the PROTOC_GEN_GO_PATH branch and the mise branch that calls `mise which
protoc-gen-go`), check the command succeeded if needed, then export
PATH="$protoc_dir:$PATH" (or export PATH="$mise_dir:$PATH"); update the two
blocks around the export PATH lines to use these separate assignment-then-export
steps so non-zero exits are not masked.

Comment on lines +107 to +109
if result != "red" {
t.Fatalf("expected 'Red', got %v", result)
}
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.

⚠️ Potential issue | 🟡 Minor

Inconsistent error message vs assertion value.

The assertion checks for "red" (lowercase) but the error message says "expected 'Red'" (capitalized). This will confuse developers when the test fails.

🐛 Fix the error message
-	if result != "red" {
-		t.Fatalf("expected 'Red', got %v", result)
+	if result != "red" {
+		t.Fatalf("expected 'red', got %v", result)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if result != "red" {
t.Fatalf("expected 'Red', got %v", result)
}
if result != "red" {
t.Fatalf("expected 'red', got %v", result)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/pkg/tests/call_function_test.go` around lines
107 - 109, The failure message is inconsistent with the asserted value: the test
asserts that result == "red" but t.Fatalf says "expected 'Red'"; update the
t.Fatalf call that references result to match the asserted lowercase value
(e.g., "expected 'red', got %v") so the error message accurately reflects the
expectation; locate the t.Fatalf call in call_function_test.go that checks the
variable result and adjust its message accordingly.

Comment on lines +16 to +22
ffi_safe_ptr(|| {
let root_path_str = unsafe { CStr::from_ptr(root_path) }
.to_str()
.map_err(|e| format!("Invalid root_path UTF-8: {e}"))?;
let src_files_str = unsafe { CStr::from_ptr(src_files_json) }
.to_str()
.map_err(|e| format!("Invalid src_files_json UTF-8: {e}"))?;
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.

⚠️ Potential issue | 🟠 Major

Add null pointer checks before CStr::from_ptr.

If the Go side passes a null pointer (e.g., due to a bug or empty string handling), CStr::from_ptr will dereference null, causing undefined behavior. While ffi_safe_ptr catches panics, null dereference may not panic on all platforms—it can cause a segfault.

🛡️ Proposed fix
 ffi_safe_ptr(|| {
+    if root_path.is_null() {
+        return Err("root_path is null".to_string());
+    }
+    if src_files_json.is_null() {
+        return Err("src_files_json is null".to_string());
+    }
     let root_path_str = unsafe { CStr::from_ptr(root_path) }
         .to_str()
         .map_err(|e| format!("Invalid root_path UTF-8: {e}"))?;
     let src_files_str = unsafe { CStr::from_ptr(src_files_json) }
         .to_str()
         .map_err(|e| format!("Invalid src_files_json UTF-8: {e}"))?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ffi_safe_ptr(|| {
let root_path_str = unsafe { CStr::from_ptr(root_path) }
.to_str()
.map_err(|e| format!("Invalid root_path UTF-8: {e}"))?;
let src_files_str = unsafe { CStr::from_ptr(src_files_json) }
.to_str()
.map_err(|e| format!("Invalid src_files_json UTF-8: {e}"))?;
ffi_safe_ptr(|| {
if root_path.is_null() {
return Err("root_path is null".to_string());
}
if src_files_json.is_null() {
return Err("src_files_json is null".to_string());
}
let root_path_str = unsafe { CStr::from_ptr(root_path) }
.to_str()
.map_err(|e| format!("Invalid root_path UTF-8: {e}"))?;
let src_files_str = unsafe { CStr::from_ptr(src_files_json) }
.to_str()
.map_err(|e| format!("Invalid src_files_json UTF-8: {e}"))?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@baml_language/crates/bridge_go/src/runtime.rs` around lines 16 - 22, Add
explicit null-pointer checks for the incoming C pointers before calling
CStr::from_ptr: inside the ffi_safe_ptr closure, test if root_path.is_null() and
src_files_json.is_null() and return an Err with a descriptive message (e.g.,
format!("Null pointer for root_path") / format!("Null pointer for
src_files_json")) instead of calling unsafe CStr::from_ptr; then proceed to call
unsafe CStr::from_ptr(...) only after each pointer is confirmed non-null. Ensure
you reference the existing symbols ffi_safe_ptr, root_path, src_files_json and
CStr::from_ptr when making the changes so the early-return error integrates with
the surrounding Result flow.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 10, 2026

Merging this PR will not alter performance

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

✅ 15 untouched benchmarks
⏩ 105 skipped benchmarks1


Comparing push-sszxsunturul (0a59b9e) with canary (d124833)

Open in CodSpeed

Footnotes

  1. 105 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions
Copy link
Copy Markdown

Binary size checks passed

7 passed

Artifact Platform Gzip Baseline Delta Status
bridge_cffi Linux 5.8 MB 5.7 MB +121.7 KB (+2.1%) OK
bridge_cffi-stripped Linux 5.8 MB 5.7 MB +98.6 KB (+1.7%) OK
bridge_cffi macOS 4.8 MB 4.6 MB +143.8 KB (+3.1%) OK
bridge_cffi-stripped macOS 4.8 MB 4.7 MB +86.7 KB (+1.9%) OK
bridge_cffi Windows 4.8 MB 4.6 MB +136.5 KB (+3.0%) OK
bridge_cffi-stripped Windows 4.8 MB 4.7 MB +89.5 KB (+1.9%) OK
bridge_wasm WASM 3.1 MB 3.0 MB +117.0 KB (+3.9%) OK

Generated by cargo size-gate · workflow run

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