Hmbown/CodeWhale · error · anyhow::Error

permissions changed after they were listed; reload {} and re

Error message

permissions changed after they were listed; reload {} and retry

What it means

remove_permission_rule (crates/config/src/lib.rs:6101) bails with 'permissions changed after they were listed' when the resolved permissions.toml no longer exists at removal time. The compare-and-remove flow (snapshot -> token -> remove) assumes the file you listed still exists; deleting or renaming it between list and remove trips this guard instead of writing a fresh file from stale state.

Source

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

    })
}

/// Remove one zero-based permission rule if `expected_token` still describes
/// that exact index in the current file.
///
/// The file is re-read only after acquiring the same adjacent lock used by
/// append operations. This makes the token check and atomic replacement one
/// transaction, preventing stale list views from deleting a different rule.
pub fn remove_permission_rule(
    config_path: Option<PathBuf>,
    index: usize,
    expected_token: &str,
) -> Result<ToolAskRule> {
    let path = resolve_permissions_path(config_path)?;
    config_document::with_config_write_lock(&path, |path| {
        let (file_exists, raw, permissions) = read_permissions_state(path)?;
        if !file_exists {
            bail!(
                "permissions changed after they were listed; reload {} and retry",
                quote_os_path(path)
            );
        }
        let rule = permissions.rules.get(index).cloned().with_context(|| {
            format!(
                "permission rule {} no longer exists in {}; list rules again",
                index + 1,
                quote_os_path(path)
            )
        })?;
        let current_token = permission_removal_token(path, &raw, index);
        if current_token != expected_token {
            bail!(
                "permissions changed after they were listed; reload {} and retry",
                quote_os_path(path)
            );
        }

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Re-run load_permissions_snapshot; a Missing file_state means there is nothing to remove — refresh the view
  2. If rules still matter, recreate permissions.toml by appending rules through the normal flow, then list and remove
  3. Serialize permission mutations through one surface so the file is not deleted under an active snapshot

Example fix

// before
let snap = load_permissions_snapshot(None)?;
// ... permissions.toml gets deleted here ...
remove_permission_rule(None, 0, &snap.removal_tokens[0])?; // -> changed after listed

// after
let snap = load_permissions_snapshot(None)?;
if snap.file_state == PermissionsFileState::Missing { /* nothing to remove; refresh UI */ }
Defensive patterns

Strategy: retry

Validate before calling

let snap = load_permissions_snapshot(None)?;
if snap.file_state == PermissionsFileState::Missing {
    // nothing to remove; refresh the view instead of calling remove_permission_rule
}

Try / catch

match remove_permission_rule(None, idx, tok) {
    Ok(rule) => { /* removed */ }
    Err(e) if e.to_string().contains("permissions changed after they were listed") => {
        let snap = load_permissions_snapshot(None)?; // reload, then retry with fresh tokens
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling remove_permission_rule(config_path, index, token) after permissions.toml was deleted, moved, or never existed (snapshot taken from a Missing file state, then remove attempted anyway).

Common situations: User or a cleanup script deletes permissions.toml while a permissions UI is open; two sessions racing where one removes the file and the other tries to remove a rule from it.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/bcfc4923cc0f73b4. Report an issue: GitHub.