Hmbown/CodeWhale · error

refusing inconsistent permission removal at

Error message

refusing inconsistent permission removal at {}

What it means

After remove_permission_rule edits the TOML document, it re-parses the generated body and asserts the result contains exactly one fewer rule than before. If the re-parse or count check fails, the library refuses to persist the edit, protecting permissions.toml from a corrupting or surprising write.

Solutions

  1. Inspect permissions.toml for unusual structure (multiple `rules` keys, odd comments between [[rules]] tables) and normalize it to a plain array of tables
  2. Re-run load_permissions_snapshot and remove the rule again from a fresh snapshot
  3. Restore permissions.toml from backup or regenerate it, then remove the rule via the API

Example fix

// before (hand-edited, ambiguous file)
rules = []
[[rules]]
tool = "bash"

// after (normalized)
[[rules]]
tool = "bash"
Defensive patterns

Strategy: validation

Validate before calling

// pre-parse permissions.toml yourself; reject ambiguous structure before calling the API
let raw = std::fs::read_to_string(&path)?;
let parsed: PermissionsToml = toml::from_str(&raw)?; // fails on non-standard shapes

Type guard

fn rules_is_array(doc: &toml_edit::DocumentMut) -> bool {
    matches!(doc.get("rules"), Some(toml_edit::Item::ArrayOfTables(_))
        | Some(toml_edit::Item::Value(v)) if v.is_array())
}

Try / catch

match remove_permission_rule(path, index, &token) {
    Err(e) if e.to_string().contains("refusing inconsistent permission removal") => {
        eprintln!("normalize permissions.toml structure, reload snapshot, retry");
    }
    other => other?,
}

Prevention

When it happens

Trigger: remove_permission_rule where parse_generated_permissions of the serialized document yields a rule count that is not exactly (original - 1), e.g. the edit removed zero or multiple rules.

Common situations: A permissions.toml with unusual structure (rules declared in a shape the editor mishandles, duplicate `rules` keys, inline-table arrays with embedded header comments) causing the round-trip to drop or merge rules; a bug in the removal path after manual hand-editing of the file.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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

Appendix: source

Thrown at crates/config/src/lib.rs:6257

        let mut document = parse_permissions_document(path, &raw)?;
        let rules_item = document.get_mut("rules").with_context(|| {
            format!(
                "permissions at {} no longer contain a rules array",
                quote_os_path(path)
            )
        })?;
        let orphaned_header = remove_permission_rule_item(rules_item, index)?;
        if let Some(header) = orphaned_header {
            let trailing = format!(
                "{header}{}",
                document.trailing().as_str().unwrap_or_default()
            );
            document.set_trailing(trailing);
        }
        let body = document.to_string();
        let persisted = parse_generated_permissions(path, &body)?;
        if persisted.rules.len() + 1 != permissions.rules.len() {
            bail!(
                "refusing inconsistent permission removal at {}",
                quote_os_path(path)
            );
        }
        write_permissions_atomic(path, body.as_bytes())?;
        Ok(rule)
    })
}

fn load_sibling_permissions(config_path: &Path) -> Result<PermissionsToml> {
    let permissions_path = checked_permissions_path_for_config_path(config_path)?;
    let (_, _, permissions) = read_permissions_state(&permissions_path)?;
    Ok(permissions)
}

fn read_permissions_state(path: &Path) -> Result<(bool, String, PermissionsToml)> {
    let file_exists = checked_path_exists(path)?;
    let raw = if file_exists {

View on GitHub (pinned to 73e0f67d83)