Hmbown/CodeWhale · error
`rules` in permissions.toml must be an array
Error message
`rules` in permissions.toml must be an array
What it means
append_permission_rule edits the `rules` item in permissions.toml. When `rules` is stored as a TOML value (inline array) it must be a TOML array so an inline table rule can be pushed; if `as_array_mut()` fails, the item is some other value type (string, integer, table).
Solutions
- Edit permissions.toml so `rules` is an array: either `rules = [ { tool = "...", ... } ]` or `[[rules]]` sections
- Remove the invalid `rules = <scalar>` line entirely so the library can create the array itself
- Re-run load_permissions_snapshot to confirm the file parses, then retry the append
Example fix
// before (permissions.toml) rules = "deny bash" // after [[rules]] tool = "bash" permission = "deny"
Defensive patterns
Strategy: validation
Validate before calling
let doc: toml_edit::DocumentMut = raw.parse()?;
match doc.get("rules") {
None | Some(toml_edit::Item::ArrayOfTables(_)) => Ok(()),
Some(toml_edit::Item::Value(v)) if v.is_array() => Ok(()),
_ => Err(anyhow::anyhow!("rules must be an array in permissions.toml")),
} Type guard
fn is_rules_array(item: Option<&toml_edit::Item>) -> bool {
matches!(item, None | Some(toml_edit::Item::ArrayOfTables(_))
| Some(toml_edit::Item::Value(v)) if v.as_array().is_some())
} Try / catch
match result {
Err(e) if e.to_string().contains("must be an array") => {
eprintln!("fix `rules` in permissions.toml: it must be [[rules]] tables or an inline array");
}
other => other?,
} Prevention
- Never hand-write `rules = <scalar>`; only arrays are valid
- Edit permissions.toml through the library API instead of manual rewrites
- After manual edits, load a snapshot to validate before further API calls
When it happens
Trigger: Calling the permission-rule append API while permissions.toml contains `rules` set to a non-array value, e.g. `rules = "x"` or `rules = true`, or `rules` defined as a plain table rather than an array/inline-array.
Common situations: Hand-editing permissions.toml and setting rules to a scalar; a different tool writing rules as a non-array; copy-pasting an invalid snippet into the config file.
Related errors
- failed to parse permissions at
- refusing inconsistent permission removal at
- active profile is missing or malformed
- agent profile tools.posture= would widen permissions; use…
- Cannot export a non-string default_model without losing its…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/e8f26a19846d4452.
Report an issue: GitHub.
Appendix: source
Thrown at crates/config/src/lib.rs:6340
hasher.update(raw.as_bytes());
let digest = hasher.finalize();
let mut token = String::with_capacity(24);
for byte in &digest[..12] {
use std::fmt::Write as _;
let _ = write!(&mut token, "{byte:02x}");
}
token
}
fn append_permission_rule(item: &mut toml_edit::Item, rule: &ToolAskRule) -> Result<()> {
match item {
toml_edit::Item::ArrayOfTables(rules) => {
rules.push(permission_rule_table(rule));
Ok(())
}
toml_edit::Item::Value(value) => {
let Some(rules) = value.as_array_mut() else {
bail!("`rules` in permissions.toml must be an array");
};
rules.push(toml_edit::Value::InlineTable(permission_rule_inline_table(
rule,
)));
Ok(())
}
_ => bail!("`rules` in permissions.toml must be an array"),
}
}
fn remove_permission_rule_item(item: &mut toml_edit::Item, index: usize) -> Result<Option<String>> {
match item {
toml_edit::Item::ArrayOfTables(rules) => {
if index >= rules.len() {
bail!("permission rule index changed before removal");
}
let file_header = if index == 0 {
rulesView on GitHub (pinned to 73e0f67d83)