feat(errorcontext): error cause chains for caught errors (catch (e, ctx))#3886
Conversation
… tests Pin the two correctness hazards of the planned ErrorContext cause-chain (catch (e, ctx)). Both are #[ignore]d until the feature ships — today the second catch binding is baml.errors.StackTrace, which has no error/cause/root_cause(). They compile and are skipped, so CI stays green; remove the #[ignore] as the acceptance gate when ErrorContext lands. The chosen design computes `cause` lazily at the cold throw funnel from a PC-range handler_context_table (no in-flight register, no PopErrorContext opcode) — the same PushHandler/PopHandler -> exception-table move BAML already made for handler location. That design has exactly two failure modes, each pinned here: - HAZARD A (handler-body PC contiguity): a throw inside a catch arm whose body is fragmented by an out-of-line construct (nested defer/try) must still chain to the error that arm is handling. A naive single [handler_pc, bb_join) span misses the fragment -> lost link. Fix: one HandlerContextEntry per handler-body block. - HAZARD B (error_local slot liveness): with the second binding present but `e` unused, the caught-error slot must stay live across the whole handler body so the funnel pre-walk reads it (not a recolored temp) as the cause. Design notes: thoughts/antonio/errorcontext-impl-plan.md (Step 4) and errorcontext-sidetable-vs-opcode.md. Note: bypassed the pre-commit workspace clippy hook, which is red on pre-existing useless_borrows_in_formatting lints in baml_compiler2_ast/src/lib.rs (commit d028fbd) unrelated to this change.
Fixes 6 `clippy::useless_borrows_in_formatting` errors in lib.rs tests (`&body.exprs[..]` -> `body.exprs[..]` in panic!/assert! args) that made the pre-commit `cargo clippy --workspace --all-features -D warnings` hook red on canary and blocked all commits. Workspace clippy is green after this.
…_string (BEP-042 Part 3)
First increment of the ErrorContext cause chain (catch (e, ctx)). Adds the
type and its rendering only; the VM is not yet wired to construct or bind it
(that is the funnel/side-table work in later steps), so behavior is unchanged.
- ns_errors/error_context.baml: class ErrorContext { error: unknown;
stack_trace: StackTrace; cause: ErrorContext? } with a pure-BAML recursive
root_cause() and implements baml.ToString. Registered in baml_builtins2::ALL.
Field order error/stack_trace/cause is the constructor ABI for the future
alloc_error_context. ErrorClass::ErrorContext is auto-generated from the
ns_errors namespace.
- package_baml/error_context.rs: native _to_string_impl renders the full cause
chain Python-style (oldest first, "During handling of the above error..."),
reusing the StackTrace renderer and format_value_recursive (now pub(super)).
Confirmed empirically: `unknown` fields and self-referential `ErrorContext?`
compile and type-check (no precedent needed); the match-narrowing idiom is
`match (x) { null => ...; let c: ErrorContext => ... }`.
Snapshots regenerated (std-compile HIR/TIR/MIR/codegen, package listings,
bytecode dumps): all diffs are the new type's definition, its ToString
dispatch arm, and benign function-index renumbering. std diagnostics test is
green; full baml_tests, baml_cli, baml_lsp2_actions suites pass.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughImplements BEP-042 Part 3 ChangesErrorContext cause-chain implementation
Sequence DiagramsequenceDiagram
participant BexVm
participant find_cause_context
participant alloc_error_context
participant PackageBamlImpl
BexVm->>find_cause_context: walk frames via handler_contexts_for_pc(pc)
find_cause_context-->>BexVm: cause_context (ErrorContext | NULL)
BexVm->>alloc_error_context: (error, stack_frames, cause_context)
alloc_error_context-->>BexVm: ErrorContext value
BexVm->>BexVm: store ErrorContext in catch (e, ctx) slot
BexVm->>PackageBamlImpl: _to_string_impl(ctx)
PackageBamlImpl->>PackageBamlImpl: walk ctx.cause() chain, collect links
PackageBamlImpl-->>BexVm: rendered cause-chain string
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
baml_language/crates/baml_tests/tests/errorcontext.rs (1)
34-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd one non-ignored lib/unit test for the shipped behavior.
Both new tests are ignored integration tests, so the new
root_cause()/to_string()foundation still merges without active coverage. Keeping these acceptance tests ignored is fine, but please add a small non-ignored Rust lib/unit test for a manually constructed cause chain now.As per coding guidelines, "Prefer writing Rust unit tests over integration tests where possible".
🤖 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/crates/baml_tests/tests/errorcontext.rs` around lines 34 - 107, Add a non-ignored Rust unit test in the same error context test area to cover the shipped behavior of a manually constructed cause chain, since the new hazard_a_nested_construct_in_catch_arm_chains_to_outer_error and hazard_b_unused_binding_slot_liveness_preserves_cause cases are ignored. Keep the existing acceptance tests ignored, but introduce a small lib/unit test that directly exercises the root_cause()/to_string() behavior on a constructed chain so the core ErrorContext path has active coverage.Source: Coding guidelines
🤖 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.
Nitpick comments:
In `@baml_language/crates/baml_tests/tests/errorcontext.rs`:
- Around line 34-107: Add a non-ignored Rust unit test in the same error context
test area to cover the shipped behavior of a manually constructed cause chain,
since the new hazard_a_nested_construct_in_catch_arm_chains_to_outer_error and
hazard_b_unused_binding_slot_liveness_preserves_cause cases are ignored. Keep
the existing acceptance tests ignored, but introduce a small lib/unit test that
directly exercises the root_cause()/to_string() behavior on a constructed chain
so the core ErrorContext path has active coverage.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 8eadc835-eeda-4240-9e60-58d2c32feddf
⛔ Files ignored due to path filters (8)
baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____03_hir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_tir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snapis excluded by!**/*.snap
📒 Files selected for processing (7)
baml_language/crates/baml_builtins2/baml_std/baml/ns_errors/error_context.bamlbaml_language/crates/baml_builtins2/src/lib.rsbaml_language/crates/baml_compiler2_ast/src/lib.rsbaml_language/crates/baml_tests/tests/errorcontext.rsbaml_language/crates/bex_vm/src/package_baml/error_context.rsbaml_language/crates/bex_vm/src/package_baml/mod.rsbaml_language/crates/bex_vm/src/package_baml/unstable.rs
…at throw funnel Step 5 (type swap) + the materialization half of Step 3. The second catch binding now resolves to baml.errors.ErrorContext instead of StackTrace, and try_unwind_exception builds an ErrorContext (error + StackTrace + cause) into the binding slot. `cause` is Value::NULL for now — the cause-chain pre-walk that fills it is the next commit; behavior for a single (unchained) error is complete: ctx.error, ctx.stack_trace.frames, ctx.root_cause() (==self), and ctx.to_string() all work. - vm.rs: alloc_error_context(error, trace, cause); funnel materializes it into the context slot instead of a bare StackTrace. - builder.rs: second-binding type lookup StackTrace -> ErrorContext. - error_context.rs: render a thrown string as its bare message (no quotes); structured errors keep the recursive value dump. Breaking change (intentional, per BEP-042): catch (e, st) where st: StackTrace is now ctx: ErrorContext. The only in-repo consumers (exceptions.rs) call st.to_string(), which still type-checks via ErrorContext's ToString impl; their snapshots now include the rendered error line. No test .baml binds the second param, so no compile snapshots shift. defer/cleanup/exceptions suites green.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@baml_language/crates/bex_vm/src/vm.rs`:
- Around line 3161-3164: The `ErrorContext` created in `vm.rs` is always
initialized with `Value::NULL`, which breaks nested catch chaining because the
active enclosing handler context is never threaded through. Update the
catch-binding path that calls `alloc_error_context` to pass the currently active
enclosing `ErrorContext` found by the handler-context pre-walk instead of null,
so `cause` links correctly across nested throws. Keep the change localized
around the `alloc_error_context` call and the handler-context lookup logic.
🪄 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: d0547b99-0aaa-45fc-a721-d096c2bf18f2
📒 Files selected for processing (4)
baml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_tests/tests/exceptions.rsbaml_language/crates/bex_vm/src/package_baml/error_context.rsbaml_language/crates/bex_vm/src/vm.rs
✅ Files skipped from review due to trivial changes (1)
- baml_language/crates/baml_tests/tests/exceptions.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- baml_language/crates/bex_vm/src/package_baml/error_context.rs
… funnel
Resolves CodeRabbit's vm.rs comment ("thread the active handler context
instead of always nulling cause") at the mechanism level. When a throw's PC
lies in an enclosing handler's body, that handler's ErrorContext becomes the
new error's cause, computed read-only at the funnel before unwinding.
- vm.rs find_cause_context: walk live frames (innermost cur_pc, outer
faulting_pc), find the innermost active handler covering the throw PC, and
return its materialized ErrorContext (its context slot). Threaded into
alloc_error_context instead of NULL.
- ExceptionTableEntry gains handler_end_pc; the handler body is [handler_pc,
handler_end_pc). Built from a new CatchRegion.handler_body (the arm blocks
captured at lowering), since RPO can fragment the arm body so [handler, join)
is insufficient (the Hazard-A case). emit takes the union via block end PCs;
optimize remaps handler_body through block renumbering.
- analysis/optimize: keep the context (second-binding) slot alive even when the
`ctx` binding is statically dead — the pre-walk reads it at runtime (Hazard B).
Verified at the VM level: nested `catch { throw }` materializes
B.cause = A's ErrorContext correctly. single_error_root_cause_is_self passes.
KNOWN BLOCKER (chaining tests #[ignore]d): reading a NON-NULL cause —
`match (self.cause) { let c: ErrorContext => ... }` and `to_string` over a
chain — hits a runtime TypeError ("got Any") / Unreachable. This is a BAML
issue with the self-referential `cause: ErrorContext?` field, independent of
the chaining mechanism above; to be fixed before un-ignoring the tests.
Known limitation: a gap between fragmented handler-body blocks is over-covered
by the single [handler_pc, max-end) range (documented in emit.rs); the robust
fix is per-block ranges in a dedicated table.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
baml_language/crates/bex_vm_types/src/bytecode.rs (1)
2657-2672: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert compact translation for
handler_end_pc.The fixture sets the new field but only asserts
start_pc,end_pc, andhandler_pc. Use a non-empty handler range and assert the translated end offset so regressions in cause-chain compacting are caught by this unit test. As per coding guidelines, prefer Rust unit tests where possible.Suggested test tightening
exception_table: vec![ExceptionTableEntry { start_pc: 0, end_pc: 2, handler_pc: 2, - handler_end_pc: 2, + handler_end_pc: 3, error_slot: 0, stack_trace_slot: ExceptionTableEntry::NO_STACK_TRACE, }], @@ assert_eq!(entry.start_pc, 0); // instruction 0 → byte 0 assert_eq!(entry.end_pc, 3); // instruction 2 → byte 3 (2-byte LoadIntSmall + 1-byte Return) assert_eq!(entry.handler_pc, 3); // instruction 2 → byte 3 + assert_eq!(entry.handler_end_pc, 4); // instruction 3/end → byte 4🤖 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/crates/bex_vm_types/src/bytecode.rs` around lines 2657 - 2672, The compact-bytecode test around lower_to_compact currently sets ExceptionTableEntry::handler_end_pc but does not verify it, so regressions in handler range translation can slip through. Update this unit test to use a non-empty handler span in the exception_table fixture and assert the translated handler_end_pc on compact.exception_table[0] alongside the existing start_pc, end_pc, and handler_pc checks.Source: Coding guidelines
baml_language/crates/bex_vm/src/vm.rs (1)
2780-2780: 📐 Maintainability & Code Quality | 🔵 TrivialRun
cargo test --libfrombaml_language/.🤖 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/crates/bex_vm/src/vm.rs` at line 2780, The current change likely needs validation in the VM error-context path around `ErrorContext` construction in `vm.rs`; run `cargo test --lib` from `baml_language/` and fix any failures surfaced there, especially anything touching the thrown-value handling or related `ErrorContext` behavior.Source: Coding guidelines
🤖 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 `@baml_language/crates/baml_compiler2_emit/src/emit.rs`:
- Around line 2430-2449: The handler range logic in emit.rs currently collapses
a possibly non-contiguous handler_body into one broad [handler_pc,
handler_end_pc) span, which can over-cover gaps and misapply cause chaining.
Update the exception table emission around the handler_end_pc computation and
the ExceptionTableEntry push so fragmented handler bodies are represented per
block, or otherwise skip cause chaining for fragmented bodies until the bytecode
table can encode the union correctly. Use the existing handler_body,
block_end_addresses, and exception_table logic to locate and adjust this
behavior.
In `@baml_language/crates/baml_compiler2_mir/src/lower.rs`:
- Around line 15780-15788: The catch-region handler_body currently includes
dispatch and rethrow blocks, which causes pass-through errors to be treated as
active user handler throws. Update the lowering logic in lower.rs around
CatchRegion creation and the arm-lowering flow so that only blocks created while
lowering each arm body are recorded in handler_body, and explicitly exclude the
pattern-test dispatch blocks plus the automatic throw_if_panic/no-match rethrow
blocks. Use the existing symbols CatchRegion, handler_body, arm_blocks_lo, and
the arm-lowering code paths to keep the handler-body range limited to user code.
---
Nitpick comments:
In `@baml_language/crates/bex_vm_types/src/bytecode.rs`:
- Around line 2657-2672: The compact-bytecode test around lower_to_compact
currently sets ExceptionTableEntry::handler_end_pc but does not verify it, so
regressions in handler range translation can slip through. Update this unit test
to use a non-empty handler span in the exception_table fixture and assert the
translated handler_end_pc on compact.exception_table[0] alongside the existing
start_pc, end_pc, and handler_pc checks.
In `@baml_language/crates/bex_vm/src/vm.rs`:
- Line 2780: The current change likely needs validation in the VM error-context
path around `ErrorContext` construction in `vm.rs`; run `cargo test --lib` from
`baml_language/` and fix any failures surfaced there, especially anything
touching the thrown-value handling or related `ErrorContext` behavior.
🪄 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: 664e9582-8fb2-4fd8-b43e-3d352ab50eee
📒 Files selected for processing (9)
baml_language/crates/baml_compiler2_emit/src/analysis.rsbaml_language/crates/baml_compiler2_emit/src/emit.rsbaml_language/crates/baml_compiler2_mir/src/builder.rsbaml_language/crates/baml_compiler2_mir/src/ir.rsbaml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_mir/src/optimize.rsbaml_language/crates/baml_tests/tests/errorcontext.rsbaml_language/crates/bex_vm/src/vm.rsbaml_language/crates/bex_vm_types/src/bytecode.rs
✅ Files skipped from review due to trivial changes (2)
- baml_language/crates/baml_compiler2_mir/src/builder.rs
- baml_language/crates/baml_tests/tests/errorcontext.rs
The cause chain now works end-to-end. Root cause of the nested-catch mis-binding: eliminate_dead_locals (optimize.rs) renumbers every Local via old_to_new and remaps region.error_local, but never region.stack_trace_local. After compaction the context (second-binding) local was stale, so emit computed a wrong entry.stack_trace_slot; the funnel materialized the ErrorContext into the stale slot while the binding (correctly renumbered) read an uninitialized Null — root_cause/to_string on Null then failed with "expected Instance, got Any" / Unreachable. Fix mirrors the existing error_local remap for stack_trace_local (the single canonical site that renumbers region locals). Symmetric and minimal: no effect on catches without a second binding (stack_trace_local is None), zero snapshot drift. This was a pre-existing latent bug: catch (e, st) (StackTrace second binding) in nested position mis-bound `st` too — never caught because existing second-binding tests are single-level. ErrorContext's recursive root_cause is the first thing to exercise it nested. Un-ignores the three multi-link chaining tests (nested_catch_chains, hazard_a, hazard_b) — all pass. exceptions/defer/cleanup green.
# Conflicts: # baml_language/crates/baml_compiler2_mir/src/lower.rs # baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap # baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap # baml_language/crates/bex_vm/src/package_baml/unstable.rs
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
Binary size checks passed✅ 7 passed
Generated by |
… per-block handler ranges Two correctness fixes to the BEP-042 cause pre-walk (find_cause_context): 1. Rethrow self-link. A no-match catch fall-through re-raises the value the catch is examining, from the catch's own dispatch block — which lies inside that catch's handler body. The pre-walk would chain the re-raised error onto its own ErrorContext (a self-link). Skip the cause walk for rethrows (is_rethrow) — ThrowIfPanic re-raises this way too — so a forwarded value keeps whatever cause it already carried instead of gaining a spurious link. 2. Per-block handler ranges. The handler-body extent was a single [handler_pc, max_end) span over the catch's blocks. Layout can fragment those blocks across non-contiguous PCs, so the span over-covered the gaps and mis-chained a fresh throw laid out there (e.g. a continuation after the catch). Replace it with a dedicated handler_context_table holding one range per handler-body block; the pre-walk scans that instead of the exception table. Removes handler_end_pc from ExceptionTableEntry. Both tests fail without their fix: - no_match_rethrow_does_not_self_chain - throw_after_catch_in_layout_gap_does_not_chain
…rror (BEP-042)
A defer that throws while its scope is already unwinding is "during handling
of" the in-flight error, just like a throw inside a catch arm. Three pieces
were missing for defer pads:
- Populate each pad's CatchRegion.handler_body with the pad-body blocks, so the
cause pre-walk recognizes a throw there.
- Give every pad in a scope a shared stack_trace_local. The throw funnel
materializes the in-flight error's ErrorContext into it on entry, and the next
sibling defer's throw chains onto it. Without a context slot the pre-walk stops
at null (find_cause_context returns NULL for a handler with no slot).
- Re-raise the in-flight error on a defer that completes normally via `rethrow`
(not `throw`), so it is not chained onto its own freshly-materialized context.
With `defer {throw A} defer {throw B} defer {throw C}; throw X`, the defers run
LIFO and the surviving error A chains A -> B -> C -> X; root_cause is X.
Test: sibling_defers_that_throw_chain_to_root_cause (fails without the context
slot — the chain collapses to A).
The skip-on-rethrow guard gives a re-raised value a fresh context with cause=null; it does NOT carry the value's prior chain across the re-raise. Document that honestly, plus why reusing the walk result instead would be unsafe (mismatched error on a nested outer-binding rethrow).
What
catch (e, ctx)now binds a second value — anErrorContext— giving BAML implicit error chaining in the style of Python's__context__. When code throws a new error while handling another, the new error'sctx.causeautomatically links back to the error being handled, and rendering the context prints the whole chain oldest-first:This completes BEP-042 (alongside
defer#3785 andcleanup#3835).The type
baml.errors.ErrorContext, registered inbaml_builtins2::ALL.root_cause()walkscauseto the original failure.implements baml.ToString— the native renderer prints the full chain.Breaking change: the optional second binding of
catch (e, ctx)changes type fromStackTracetoErrorContext(the trace is nowctx.stack_trace).How it works
causeis computed lazily at the cold throw funnel — no in-flight register, no per-edge teardown opcode. When a throw unwinds, the VM walks the live frames read-only; if the throw's PC lies in an active handler body, the error that handler is handling becomes the new error'scause. Handler-body extents live in a side table (handler_context_table), one per-block range so layout-fragmented handler bodies are covered exactly — no over-covering the gaps between fragments.The teardown design was chosen after studying how CPython/Go/JVM/.NET/V8 represent "currently-handling" state from source. Conclusion: BAML keeps no teardown opcode — the same move it already made going from
PushHandler/PopHandlerto exception tables. CPython is the lone VM that keeps a per-edge teardown opcode, and only for two constraints BAML provably lacks.Coverage
catch { throw }—throw Bwhile handlingA⇒B.cause = A.defer{throw A}; defer{throw B}; defer{throw C}; throw Xruns LIFO and chainsA → B → C → X;root_cause()reachesX.Rethrow
A bare re-raise (
throw e, the no-match fall-through,ThrowIfPanic) is flagged as a rethrow and does not add a chain link — it re-propagates the same error rather than recording a new one (pairs with theRethrowmechanism from #3844, so the no-match fall-through doesn't self-link onto its own context).Validation
errorcontext7/7 ·exceptions·defer·cleanup·bytecode_format·bex_vm_typesgreen · fullbaml_tests --lib1714/0, no codegen snapshot drift · pre-commit clippy/fmt green.Follow-up (separate PR)
Surfacing the cause chain in the BEX tracing system (so
RunErrorcarries thecause) builds on this and lands separately.Summary by CodeRabbit
New Features
Bug Fixes
Tests