Hmbown/CodeWhale · error

snapshots carry one removal token per rule

Error message

snapshots carry one removal token per rule

What it means

Panic from `snapshot.removal_token(index)` in `remove_permission`. The code asserts an internal invariant: every rule present in the permission snapshot has a corresponding removal token. If the rules list and token list ever disagree in length, the lookup returns None and the expect panics.

Solutions

  1. Fix snapshot construction so removal tokens are generated for every rule
  2. Check `snapshot.rules().len()` equals the token count before indexing
  3. Replace the expect with graceful handling returning `rule_not_found` if tokens can legitimately be absent

Example fix

// before
let token = snapshot
    .removal_token(index)
    .expect("snapshots carry one removal token per rule");
// after
let Some(token) = snapshot.removal_token(index) else {
    return rule_not_found(app, display_index);
};
Defensive patterns

Strategy: type-guard

Validate before calling

debug_assert_eq!(snapshot.rules().len(), snapshot.removal_token_count(), "rules and removal tokens out of sync");

Type guard

fn removal_token(snapshot: &Snapshot, index: usize) -> Option<String> { snapshot.removal_token(index) }

Try / catch

let Some(token) = snapshot.removal_token(index) else { return rule_not_found(app, display_index); };

Prevention

When it happens

Trigger: Invoking `/permissions remove <index>` where the snapshot at that index has no removal token — i.e., snapshot rules and removal tokens were built out of sync when the snapshot was constructed.

Common situations: A code change added a rule source without extending removal-token generation; concurrent mutation of permissions between snapshot creation and removal command handling.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/src/commands/groups/config/permissions.rs:55

    }
    let Ok(display_index) = parts[1].parse::<usize>() else {
        return usage_error(app);
    };
    let Some(index) = display_index.checked_sub(1) else {
        return rule_not_found(app, display_index);
    };

    if parts.len() == 2 {
        let snapshot = match load_snapshot(app) {
            Ok(snapshot) => snapshot,
            Err(error) => return operation_error(app, &error),
        };
        let Some(rule) = snapshot.rules().get(index) else {
            return rule_not_found(app, display_index);
        };
        let token = snapshot
            .removal_token(index)
            .expect("snapshots carry one removal token per rule");
        let command = format!("/permissions remove {display_index} --confirm {token}");
        let rule = format_rule(app, display_index, rule);
        let message = tr(app.ui_locale, MessageId::PermissionsRemovePreview)
            .replace("{index}", &display_index.to_string())
            .replace("{rule}", &rule)
            .replace("{command}", &command);
        return CommandResult::message(message);
    }

    if !parts[2].eq_ignore_ascii_case("--confirm") || parts[3].is_empty() {
        return usage_error(app);
    }
    let removed =
        match codewhale_config::remove_permission_rule(app.config_path.clone(), index, parts[3]) {
            Ok(rule) => rule,
            Err(error) => return operation_error(app, &error),
        };
    let message = tr(app.ui_locale, MessageId::PermissionsRemoved)

View on GitHub (pinned to 73e0f67d83)