FFX Plugin Error Construction Guide

This document provides a comprehensive manual for ffx plugin authors on how to construct, format, and propagate errors within the ffx multi-tool architecture framework.

Core Philosophy: User-Facing Errors vs. Internal Bugs

The ffx front-end main execution loop processes all errors returned from plugins using anyhow::Error objects. The runtime framework evaluates the error object payload using downcasting:

  1. Actionable User Errors (FfxError): If the error downcasts successfully to FfxError::Error, the framework treats it as an expected operational failure. The message is printed directly to the user terminal stderr cleanly, free of engineer stack traces.
  2. Unexpected Bugs: All other unmapped error types (e.g., raw network errors, filesystem errors, un-downcast anyhow::Error) are treated as internal tool BUGS. The framework automatically attaches a BUG: stack trace prefix and instructs the user to file a Buganizer ticket at go/ffx-bug.

Rule of Thumb: If the failure is caused by bad user input, missing local file configurations, target unavailability, or anything actionable by the end-user, it MUST be returned as an explicit FfxError using the macro utilities below.

Error Macro Utilities

The errors library crate (ffx_error) provides four main macro entries optimized for plugin development:

1. ffx_error!

Use this to construct a standalone FfxError instance with a simple text string. By default, it associates the failure with an exit status code of 1.

use errors::ffx_error;

// Plain string message error
let err = ffx_error!("Target device socket connection refused.");

// Formatted template message error
let err_fmt = ffx_error!("Failed to open file: {}", path.display());

2. ffx_error_with_code!

Use this when the subcommand needs to return a specific non-zero exit status code back to the host shell wrapper script layer.

use errors::ffx_error_with_code;

// Returns a custom exit code 2 indicating entry mapping target missing
let err = ffx_error_with_code!(2, "Configuration target key not found.");

3. ffx_bail!

A highly convenient control flow macro that constructs an ffx_error!, wraps it in an Err(...) enum variant, and instantly triggers an early return (return Err(...)) from the active function context block.

use errors::ffx_bail;

if !manifest_path.exists() {
    ffx_bail!("Staged flashing manifest path '{}' does not exist.", manifest_path.display());
}

4. ffx_bail_with_code!

Combines custom exit status status codes with immediate control flow bailing early termination.

use errors::ffx_bail_with_code;

if value.is_null() {
    ffx_bail_with_code!(2, "Configuration target key contains no value data mapping.");
}

Structuring Descriptive Error Strings

To guarantee optimal user ergonomics, all error text blocks should follow the imperative style guide rules:

  • Context First: Explicitly detail what operation failed before naming the low-level trigger.
  • Actionable Hints: Provide explicit instructions or alternative parameters commands if possible (e.g., "Run ffx doctor --restart-daemon to reset connection state.").
  • No Engineering Traces: Keep raw logs, stack dumps, and module path identifiers inside the background file logging layers (ffx.log), preserving clean streams for the user console.