astrid-runtime/astrid · error

native provider returned an invalid structured error

Error message

native provider returned an invalid structured error

What it means

When a native provider returns a Failure outcome, render_response validates the structured error: the code must be lowercase ASCII letters/digits/hyphens and the message must be non-empty, at most 4096 bytes, and free of control characters. If any check fails, the CLI cannot safely render the provider's error and bails with this generic message.

Source

Thrown at crates/astrid-cli/src/commands/storage.rs:327

        }) => println!(
            "mount {mount_id} at {}: {access:?}, dirty={dirty}",
            mountpoint.display()
        ),
        StorageProviderOutcomeV1::Success(StorageProviderSuccessV1::Unmounted { mount_id }) => {
            println!("unmounted {mount_id}");
        },
        StorageProviderOutcomeV1::Failure(failure) => {
            if failure.code.is_empty()
                || failure.code.len() > 64
                || !failure
                    .code
                    .bytes()
                    .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
                || failure.message.is_empty()
                || failure.message.len() > 4096
                || failure.message.chars().any(char::is_control)
            {
                bail!("native provider returned an invalid structured error");
            }
            eprintln!(
                "storage provider error [{}]: {}",
                failure.code, failure.message
            );
            return Ok(ExitCode::FAILURE);
        },
    }
    Ok(ExitCode::SUCCESS)
}

fn platform_provider_name() -> &'static str {
    #[cfg(target_os = "macos")]
    {
        "astrid-storage-provider-fskit"
    }
    #[cfg(target_os = "linux")]
    {

View on GitHub (pinned to affd8760f4)

Solutions

  1. Fix the provider to emit kebab-case error codes ([a-z0-9-]) and sanitize messages (strip control chars, truncate to 4096 bytes)
  2. Ensure the provider replaces newlines in error messages (e.g. with spaces) before serializing JSON
  3. Provide a meaningful non-empty fallback message when no detail is available
  4. Log the raw provider output separately if you control both sides, so invalid payloads can be diagnosed

Example fix

// before (provider)
message: format!("failed: \n{raw_os_error_output}"),
// after
let msg: String = raw_os_error_output.chars().filter(|c| !c.is_control()).take(4096).collect();
message: if msg.is_empty() { "unknown provider failure".into() } else { msg },
Defensive patterns

Strategy: validation

Validate before calling

fn structured_error_ok(code: &str, msg: &str) -> bool { code.bytes().all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-') && !msg.is_empty() && msg.len() <= 4096 && !msg.chars().any(char::is_control) }

Type guard

fn sane_failure(f: &StorageProviderFailureV1) -> Option<&StorageProviderFailureV1> { if structured_error_ok(&f.code, &f.message) { Some(f) } else { None } }

Try / catch

match sane_failure(&failure) {
    Some(f) => eprintln!("storage provider error [{}]: {}", f.code, f.message),
    None => eprintln!("provider returned an unrenderable structured error"),
}

Prevention

When it happens

Trigger: A provider Failure outcome has a code containing uppercase/underscore/space characters, or an empty/oversize (>4096 bytes) message, or a message containing control characters (e.g. embedded newline in provider JSON output) — validated in render_response, exercised by the fskit_gap/newline/oversize tests.

Common situations: Provider emits raw OS error strings containing newlines or terminal control codes; provider uses CamelCase or snake_case error codes instead of kebab-case; provider serializes a multi-MB log blob as the failure message; provider returns empty message when it has no detail.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/865615e63c3d0785. Report an issue: GitHub.