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.

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.

Appendix: 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)