Hmbown/CodeWhale · error

typed config did not serialize to a TOML table

Error message

typed config did not serialize to a TOML table

What it means

config_document serializes the typed ConfigToml to TOML, re-parses it, and expects the top-level value to be a table. If toml::to_string produced anything other than a root table (an internal invariant — a struct always serializes to a table), the export/import pipeline bails rather than proceeding with a malformed document. The round-trip exists to avoid double-encoding datetime values held in flattened extras.

Solutions

  1. Inspect the config file for structurally invalid top-level content (an array or bare value instead of key/value sections) and fix it.
  2. Verify the codewhale-config crate version matches the CLI; a mismatched pair can change serialization shape.
  3. If it reproduces with a stock config, report it as a bug — this path is an internal invariant, not user input validation.
  4. Regenerate a clean config (back up first) and retry the export/import.

Example fix

// before (config.toml root)
[[somewhere]]
key = "value"
// after (root must be a table of sections)
[provider]
id = "openai"
Defensive patterns

Strategy: try-catch

Type guard

if let toml::Value::Table(table) = &parsed {
    // proceed with table
}

Try / catch

match config_document(config) {
    Ok(doc) => { /* use doc */ }
    Err(e) if e.to_string().contains("did not serialize to a TOML table") => {
        eprintln!("serialization invariant broken; check codewhale-config version / config shape: {e:#}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: export_bundle, prepare_import, or apply_config_value calls config_document and the toml round-trip yields a non-table root — essentially only possible when the serialized output is an array or scalar, which for ConfigToml would indicate a serde/serialization bug or a corrupted typed config.

Common situations: Rare: a codewhale-config version change altering the serializer's output shape; a hand-edited or corrupted config source that deserializes into an unexpected shape; a bug in custom serde impls.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/54f3a231a8026e90. Report an issue: GitHub.

Appendix: source

Thrown at crates/cli/src/config_bundles.rs:968

    }
    if key.starts_with("auth.") {
        return ExportSection::Drop;
    }
    match scope {
        BundleScope::Global => ExportSection::Global,
        BundleScope::Project => ExportSection::Project,
    }
}

fn config_document(config: &ConfigToml) -> Result<toml::map::Map<String, toml::Value>> {
    // Serialize through TOML text before parsing to Value. Direct
    // `Value::try_from` double-encodes datetime values held inside flattened
    // `toml::Value` extras as the serializer's private marker table.
    let text = toml::to_string(config).context("serializing typed config for bundle")?;
    let value: toml::Value =
        toml::from_str(&text).map_err(|_| anyhow!("serialized typed config was not valid TOML"))?;
    let toml::Value::Table(mut table) = value else {
        bail!("typed config did not serialize to a TOML table");
    };
    // `selected_provider_id` is runtime parse state and is skipped by serde;
    // restore the exact named-provider identity that ConfigStore writes.
    table.insert(
        "provider".to_string(),
        toml::Value::String(config.provider_id().to_string()),
    );
    Ok(table)
}

/// Return a recursively scrubbed export value. Secret-bearing leaves and
/// machine-local paths are omitted rather than replaced with a placeholder,
/// because a placeholder would become literal config on re-import.
fn sanitize_export_value(path: &str, value: &toml::Value) -> Option<toml::Value> {
    sanitize_export_value_at(path, value, 0)
}

fn sanitize_export_value_at(path: &str, value: &toml::Value, depth: usize) -> Option<toml::Value> {

View on GitHub (pinned to 73e0f67d83)