The Soroban debugger treats plugins as extensions, not as part of the trusted execution core.
The debugger currently escalates these plugin-specific incidents:
- plugin panics,
- plugin invocation timeouts.
Regular plugin-returned errors are still reported, but they do not automatically count as crash-isolation incidents.
When a plugin panics or exceeds its execution budget:
- the incident is captured and classified as a plugin-layer failure,
- the affected plugin is disabled for the current process/session,
- the core debugger continues running,
- a structured incident report is emitted through logging and telemetry.
This is intentionally explicit so users are not misled into thinking that a plugin crash means the Soroban debugger itself became unstable.
Understanding what happens to the event that was being dispatched at the moment a plugin trips the circuit breaker is important for reasoning about plugin output and session state.
A panic is caught synchronously inside catch_unwind during the current
dispatch call. The sequence is:
- The plugin's
on_eventhandler panics. catch_unwindcatches the panic before it can unwind into the debugger.- The panic is converted to a
PluginError::Panicand passed torecord_outcome. record_outcomeimmediately setscircuit_open = trueandsession_disabled = truefor that plugin.- An incident report is emitted and a
Panictelemetry entry is appended to the currentEventContext. - The dispatch loop moves on to the next plugin in the same cycle — other plugins are not affected.
The triggering event is therefore partially processed: the panicking plugin did not complete its handler, but every other plugin that was registered before it in the dispatch order already ran, and every plugin registered after it still runs normally.
Timeout detection is post-hoc: the plugin runs to completion and its result is returned, but if the elapsed time exceeds the configured budget the result is discarded and the plugin is session-disabled. The sequence is:
- The plugin's
on_eventhandler returnsOk(()). record_outcomecompares elapsed time againsthook_timeout(default: 250 ms).- If elapsed > timeout, the successful return value is discarded, the plugin
is session-disabled, and a
Timeoutincident report is emitted. - A
Timeouttelemetry entry is appended to the currentEventContext. - The dispatch loop continues normally for all other plugins.
Because the plugin already ran to completion, any side effects it produced (writes to its own internal state, log output, etc.) are not rolled back. Only the return value is discarded.
Once a plugin is session-disabled, every subsequent dispatch call checks
circuit_open || session_disabled at the top of run_hook_with_policy before
doing any work:
- If the circuit is open, the hook is skipped entirely —
on_eventis never called. - A
SkippedCircuitOpentelemetry entry is appended to theEventContextso the skip is visible in telemetry. - Commands and formatters from a disabled plugin return
PluginError::SessionDisabledorPluginError::CircuitOpenimmediately.
The plugin remains disabled for the lifetime of the process. There is no automatic reset or retry within a session.
The registry tracks two related but distinct flags per plugin:
| Flag | Set by | Meaning |
|---|---|---|
circuit_open |
Panic, timeout, or N consecutive failures | Invocations are skipped |
session_disabled |
Panic or timeout (incident-level events only) | Permanent skip for this session; shown in incident report |
A plugin can have circuit_open = true without session_disabled = true if
it accumulated enough consecutive soft failures (default threshold: 3). In that
case the circuit may reset on a subsequent success. A plugin with
session_disabled = true will never reset within the session regardless of
later behavior.
Every skipped or failed invocation appends a PluginTelemetryEvent to the
EventContext that was passed into the dispatch call. The telemetry entry
includes:
- plugin name,
- invocation kind (
Hook,Command, orFormatter), - outcome (
Panic,Timeout,Failure,SkippedCircuitOpen, orSuccess), - elapsed duration in milliseconds,
- a human-readable message (the incident summary line for panics and timeouts).
This means the caller always has a record of what happened, even when the plugin was silently skipped.
Session disablement is intentionally conservative:
- a panicking plugin is disabled immediately,
- a timed-out plugin is disabled immediately,
- subsequent invocations are skipped for the rest of the session.
This keeps the debugger usable while preventing repeated plugin failures from polluting the debugging experience.
Each report includes:
- plugin name,
- plugin version when available,
- plugin library path when available,
- invocation kind (
hook,command, orformatter), - incident type (
panicortimeout), - action taken,
- an explicit statement that the core debugger remains available.
Plugins are powerful, but they should never blur the trust boundary.
Clear incident reporting helps users answer two separate questions quickly:
- Did the plugin fail?
- Is the core debugger still trustworthy?
The expected answer after a contained incident is:
- yes, the plugin failed,
- yes, the core debugger is still available.
- Plugin System API Documentation — full plugin trait reference
- Plugin Command Namespace Policy — command name conflict resolution