---
title: "Introducing scripting workflows for smarter debugging"
slug: "introducing-scripting-workflows-for-smarter-debugging"
blurb: "We have recently launched a new workflow step for introspecting fatal app issues with a twist— rather than using predefined building blocks, the actual logic is written as a script, to capture exactly what matters for your team."
cover:
  url: "/assets/posts/introducing-scripting-workflows-for-smarter-debugging/feature-script_workflow-desktop@1x.webp"
  alt: "scripting workflow step"
socialThumbnail:
  url: "/assets/posts/introducing-scripting-workflows-for-smarter-debugging/feature-script_workflow-desktop@2x.webp"
  alt: "scripting workflow step"
author:
  - "delisa"
publishedDate: "2026-08-18T10:00:00.000Z"
modifiedDate: "2026-08-18T10:00:00.000Z"

---

Today we're announcing scripting workflows for issues and Ripsaw, a language for
workflow customization. Building a scripting language into a hot code path like
crash processing raises several engineering challenges, particularly around
performance, safety, and creating a testing environment prior to deployment.
Read on to learn about those challenges, how we solved them, and how you can get
started with issue debugging with scripting workflows.

## Issue workflows

Workflows let you define a sequence of conditions and actions based on observed
application behaviors, like turning frustrated taps on unresponsive buttons into
plot charts or alerts. Workflow execution took place solely within an app on a
mobile device, as the device was the primary source of information.

However, debugging fatal app events like crashes or termination by the operating
system for excessive memory use would be difficult on a mobile device, as
during the event the state of the application is unsuitable for immediate
analysis and the device lacks the kind of information which would enrich a
debugging session, like class and function names, file paths, and line numbers.

This is the basis for [Issue
workflows](https://docs.bitdrift.io/product/workflows/scripting/overview), a new
workflow type and iteration environment for building insights into and
monitoring for fatal app issues. The goal of this project is to be able to take
an issue affecting application performance and surface team- or app-specific
metrics, like the components or features which create problems in combination.

## The internals: running workflow server-side

Issue workflows are the first implementation of running a workflow server-side,
rather than on the device. It makes sense: introspecting the report generated by
an issue requires resources which only exist outside the device, like the
ability to replace the addresses and minified names in stack traces with symbol
names and file information.

Running a workflow server-side also raised new questions: What does it mean for
a workflow to run during issue processing, when the flow takes places only
during the processing of a single issue rather than as part of a unified session
with other events? What does this mean for the device state, as understood by
the workflow engine?

As much as possible, our answer has been to make the state of the workflow be
the state of the device at the time the issue occurred. It also meant some
reworking of the internals (and a lot of conversion of behaviors into Rust
traits) to support the different kinds of environments in which a workflow can
be executed. For example, where the on-device implementation of a workflow
engine would query operating system-specific APIs for device state, a
server-side implementation gathers state from the snapshot in a crash report and
the associated session. From here we could run a workflow!

The processing flow looks roughly like:

0. Deploy an issue workflow
1. An app on a device crashes or otherwise encounters an error
2. The app is relaunched, and the generated report is uploaded
3. The crash report is processed into an issue, adding class and function names,
   and file information
4. Issue workflows run, inspecting the processed report and generating metrics
   as needed
5. Processing completes and the issue is visible on dashboards, et cetera

The position in the flow makes it possible to introspect the issue while still
allowing for customization before finalizing processed data. However, crash
report processing is an extraordinarily hot code path, which presents unusual
constraints for the programs we can run.

_How could we provide an expressive environment for adding custom metrics while
restricting the kinds of errors which could occur, ideally with a
context-specific library of available functions?_

## Language and engine design

The requirements for the language itself were deceptively simple, it needed to:

* be _fast_ (for both compilation and execution)
* minimize the kinds of runtime errors which could occur, and
* be extensible, since we would likely reuse this work for other scriptable workflow steps

For this initial design, we took the shorter path and extended an existing
language, forking the excellent [Vector Remap Language](https://vrl.dev) (VRL).
We pared down the log parsing functionality of the default standard library and
instead built on it as a compact language for introspecting issues and emitting
metrics named
**[Ripsaw](https://docs.bitdrift.io/product/workflows/scripting/overview)**.

VRL (and thus Ripsaw) is implemented in Rust using the
[LALRPOP](https://lalrpop.github.io/lalrpop/) parser generator, a combination
which granted robust type safety to scripts from the get-go. VRL also has the
concept of path expressions, intended for representing the location of a value
within objects and arrays, along with type hinting for known object paths. These
features gave us a foundation to make the inputs to a script any kind of defined
object, and to limit certain functions to particular inputs (and outputs).

### Script inputs and outputs

For this first application of scripting in workflows, we had a well-defined
input (the processed crash report object) and desired output (metrics, which are
key/value pairs), and a scripting language which supported defining type hints
for the input paths. From there, we landed on a few basic definitions:

0. The `Scriptable` trait. This must be implemented for any object which could
   be introspected as a script input. A scriptable object should be able to take
   the components of a path and map them to its own fields. For example, since a
   crash report object contains an `errors` collection, the path `.errors[0]`
   should return the first error in an array, and be inspectable in a
   `for_each()` or `filter()` loop.

   ```rust
   /// Base definition for objects passable to `Script::run()`
   pub trait Scriptable {
     /// Return a value for a (potentially) nested value or self when empty
     fn resolve(&self, path: &[OwnedSegment]) -> Result<Option<ScriptValue>, PathError>;

     /// Define type definitions which will be used as hints within the scripting
     /// engine
     fn schema() -> Kind;
   }
   ```

1. The compiled `Script` (and `ScriptOutput`), resolves any compilation errors
   or type ambiguities at save-time, so the resulting object and syntax tree can
   be cached for reuse. The input type provides a `schema()` function which is
   used to provide type hints during compilation and ensures that a particular
   script can only be run with inputs of the same type.

   ```rust
   pub trait ScriptOutput: Default + Debug {}

   impl Script {
     /// Create a new script
     pub fn new<T: Scriptable>(
       program_source: &str,
       custom_functions: Vec<Box<dyn Function>>,
     ) -> anyhow::Result<Self>;

     /// Run a script, providing an input object and desired output type
     pub fn run<T: Scriptable, O: ScriptOutput + 'static>(&self, object: &T) -> anyhow::Result<O>;
   }
   ```

From this point we could generate `Scriptable` implementations for the
structures in a crash report from its storage format specification and create a
simple output object containing a key/value map for setting metrics from
context-specific functions.

The first such function we added was the (decidedly
[impure](https://en.wikipedia.org/wiki/Purely_functional_programming))
[`add_field()`](https://docs.bitdrift.io/product/workflows/scripting/functions#add_field),
which expects a report (including the
[feature flags](https://docs.bitdrift.io/sdk/features/feature-flags.html) and
[fields](https://docs.bitdrift.io/sdk/features/fields#global-fields) active at
the time the issue or crash occurred) generated from a crash as input and
inserts key/value pairs as metrics to the output object.

Given the prior work on the type design for the script engine, adding new custom
functions bordered on boring, in a good way. This left the actual experience of
developing scripts.

_How could we make it straightforward to take an investigation into an issue
where you are looking at stack traces and turn those into a live plot of
relevant metrics for debugging?_

## Editing and testing scripts

The default editing experience for issue workflows starts with a component
similar to other workflow steps, with support for freeform text editing and
lightweight syntax highlighting.

Rapid iteration on a development task though generally requires a snappy way to
test assumptions about the data and desired outcomes. Bringing this experience
to the workflow editor involved adding a new Testing Mode to the issue matching
condition which runs the condition's script against an existing issue.

<Image alt="Issue workflow with script step" altAsCaption asset="/assets/posts/introducing-scripting-workflows-for-smarter-debugging/script-workflow@1x.webp" />

<Image alt="Testing Mode" altAsCaption asset="/assets/posts/introducing-scripting-workflows-for-smarter-debugging/script-testing@1x.webp" />

The selected issue can be refined using
[issue filters](https://docs.bitdrift.io/product/issues/filtering)
including using the URL or ID of an existing issue, and the inspected field
names and values are the input paths for the script. Running a test then reveals
any build or runtime issues which would occur after deployment when processing
similar reports.

Developing Testing Mode benefited from the existing React components for issue
search and workflow steps. Running a test executes the exact same script engine
that will be used after deployment. If runtime errors occur after that point,
they are displayed alongside the chart outputs of the workflow, for triage and
further iteration as needed, closing the development iteration loop.

Outside of the workflow editor, there are
[API endpoints](https://docs.bitdrift.io/api/bitdrift_public_unary_workflows_v1_IssueMatch)
and
[Agentic skills](https://docs.bitdrift.io/product/skills/overview) for development from
the comfort of a favorite terminal emulator or text editor. Given the
compactness of the language, it is also more than possible to add a language
server implementation to provide an integrated development experience in the
future.

## Conclusions

This has been a few first steps towards making more types of workflows and
events scriptable. Next, we are looking at customization for issue grouping and
team assignment during issue triage, along with other refinements to the issue
debugging process using the power of workflows.

Learn more:

* Check out a sample issue workflow in [the sandbox](https://bitdrift.io/sandbox)
* Start a [free trial](https://bitdrift.io/signup) of bitdrift Capture for
  mobile observability
* [Get in touch for a demo](https://bitdrift.io/contact-us)

---

## The wrap

### What are issue workflows?

Issue workflows are a workflow type triggered by fatal app issues, like a crash
or termination by the operating system for resource overuse. They execute during
crash report processing, allowing yuou to inspect the processed data and
generate custom metrics.

### What is Ripsaw?

Ripsaw is a script language built by bitdrift. It is a fork of Vector Remap
Language (VRL) adapted for workflow customization rather than log parsing.

### Can I test an issue workflow before deploying it?

Yes. The workflow editor includes a Testing Mode which runs a script against an
existing issue, using the same engine as will be used after deployment. Any
build or runtime errors appear alongside metric output, so you can iterate
before deploying.

### What can an issue workflow do with crash report data?

A Ripsaw script in an issue workflow can inspect fields in a processed crash
report, such as stack traces, feature flags, and device state at the time of the
crash, and use that data to compute and emit custom metrics.

### Do I need to create issue workflows through the bitdrift UI?

No. In addition to the workflow editor, bitdrift provides API endpoints and CLI
tooling for writing and testing Ripsaw scripts from a terminal or text editor.
