astrid-runtime/astrid · error · io::Error

layout migration {label} must be non-empty printable text

Error message

layout migration {label} must be non-empty printable text

What it means

LayoutMigrationTarget::new in crates/astrid-core/src/dirs_layout.rs validates that the 'store format' and 'binary identity' labels of a migration target are non-empty strings without control characters. An empty value or one containing control chars (e.g. \\0, \\n, ANSI escapes) fails with ErrorKind::InvalidInput, since these labels are persisted in migration records and must be safe, human-readable text.

Source

Thrown at crates/astrid-core/src/dirs_layout.rs:69

    ///
    /// # Errors
    ///
    /// Returns an error when either durable identity is empty or contains
    /// control characters that would make operator rendering ambiguous.
    pub fn new(
        store_format: impl Into<String>,
        binary_identity: impl Into<String>,
    ) -> io::Result<Self> {
        let target = Self {
            store_format: store_format.into(),
            binary_identity: binary_identity.into(),
        };
        for (label, value) in [
            ("store format", target.store_format.as_str()),
            ("binary identity", target.binary_identity.as_str()),
        ] {
            if value.is_empty() || value.chars().any(char::is_control) {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    format!("layout migration {label} must be non-empty printable text"),
                ));
            }
        }
        Ok(target)
    }

    /// Bind a migration to the exact executable bytes performing the cutover.
    ///
    /// # Errors
    ///
    /// Returns an error when the current executable cannot be resolved or read,
    /// or when the supplied store-format identity is invalid.
    pub fn for_current_executable(store_format: impl Into<String>) -> io::Result<Self> {
        let executable = std::env::current_exe()?;
        let mut file = File::open(&executable)?;
        let mut hasher = blake3::Hasher::new_derive_key("astrid layout migration binary v1");

View on GitHub (pinned to affd8760f4)

Solutions

  1. Pass non-empty, printable strings for both store_format and binary_identity.
  2. Trim and sanitize source values (reject/replace control characters) before constructing the target.
  3. Provide a hardcoded fallback constant when build metadata is unavailable.
  4. Validate the upstream config/env source so empty values never reach the constructor.

Example fix

// before
let target = LayoutMigrationTarget::new(env::var("STORE_FORMAT")?, identity_with_banner)?;

// after
let fmt = env::var("STORE_FORMAT")?;
let fmt = fmt.trim().to_string();
if fmt.is_empty() || fmt.chars().any(char::is_control) {
    return Err("STORE_FORMAT must be non-empty printable text".into());
}
let target = LayoutMigrationTarget::new(fmt, sanitized_identity)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust
fn valid_label(value: &str) -> bool {
    !value.is_empty() && !value.chars().any(char::is_control)
}
// call before construction:
assert!(valid_label(&store_format) && valid_label(&binary_identity));

Type guard

fn is_printable_nonempty(s: &str) -> bool {
    !s.is_empty() && s.chars().all(|c| !c.is_control())
}

Try / catch

match LayoutMigrationTarget::new(fmt, identity) {
    Err(e) if e.kind() == io::ErrorKind::InvalidInput => {
        // fall back to default constants and rebuild the target
    }
    other => other?,
}

Prevention

When it happens

Trigger: Constructing LayoutMigrationTarget::new with store_format or binary_identity set to "" or strings containing control characters — typically from unparsed config, raw bytes from a file, or format!-built strings with embedded newlines.

Common situations: Loading store format from an environment variable or config file that is unset/empty; binary identity derived from build metadata that is missing in dev builds; concatenating multi-line version banners into the identity field.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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