Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions sdk/node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@
//! const result = await session.send('What files handle auth?');
//! console.log(result.text);
//! ```
//!
//! ## Panic safety at the FFI boundary
//!
//! napi 2.x does **not** wrap exported bodies in `catch_unwind` by default. A
//! Rust panic that reaches the `extern "C"` boundary aborts the whole Node
//! process (Rust ≥ 1.81) — it does *not* become a catchable JS error. Only two
//! contexts are panic-safe: a `#[napi]` **async** fn / `impl Future` (panic →
//! rejected Promise) and a sync fn explicitly tagged `#[napi(catch_unwind)]`.
//! Everything else aborts (or silently loses the panic): default **sync**
//! `#[napi]` fns, `ThreadsafeFunction` callbacks (a panic there — or a
//! return-value conversion `Err` — aborts via `napi_fatal_error` under *both*
//! `ErrorStrategy` variants), `tokio::spawn`'d task bodies (panic swallowed,
//! never surfaced), `Drop`/finalizers, and module init.
//!
//! Convention this crate follows so the boundary stays safe: never
//! `.unwrap()` / `.expect()` / `panic!` in those contexts. Propagate with `?`
//! into a `napi::Error`, or fail closed with `unwrap_or_else` inside
//! threadsafe callbacks. (Audited 2026-05: the only production panic site is
//! the lazy Tokio-runtime build in `fallback_runtime()`, reached from within
//! `#[napi]` bodies; the spawned-task and threadsafe-callback paths are
//! panic-free by construction.)

#[macro_use]
extern crate napi_derive;
Expand Down
17 changes: 17 additions & 0 deletions sdk/python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@
//! result = session.send("What files handle auth?")
//! print(result.text)
//! ```
//!
//! ## Panic safety at the FFI boundary
//!
//! PyO3 0.23 wraps `#[pyfunction]` / `#[pymethods]` / `#[pymodule]`-init bodies
//! in `catch_unwind`, so a panic there surfaces as a Python `PanicException`
//! (a `BaseException` subclass) rather than UB. It does **not** cover panics
//! inside `std::thread` / `tokio::spawn` task bodies, or `Python::with_gil`
//! closures invoked from a worker thread *outside* a pyfunction frame — those
//! are silently lost, and a panicking `Drop` during an unwind aborts the
//! process.
//!
//! Convention this crate follows so the boundary stays safe: the Rust→Python
//! bridges that run on tokio worker threads (`PythonCallbackHandler`,
//! `PyBudgetGuard`, `PySlashCommand`) never `.unwrap()` / `panic!`; they use
//! `.ok()` / `unwrap_or_else` and fail closed. (Audited 2026-05: the only
//! production panic site is the lazy Tokio-runtime build in `get_runtime()`,
//! reached only from caught pyfunction frames.)

use a3s_code_core::commands::{
CommandContext as RustCommandContext, CommandOutput as RustCommandOutput,
Expand Down
Loading