Hmbown/CodeWhale · error

; additionally failed to restore prior secret-store state…

Error message

{error}; additionally failed to restore prior secret-store state for {slot}: {rollback}

What it means

When the config save fails and rollback verification shows the slot still contains the new key, the code attempts to restore the snapshotted prior secret (`set` or `delete`). If that restore call fails, this error reports the restore failure alongside the original save error.

Solutions

  1. Inspect the secret slot in the backend: it may still hold the new key while the config was not saved.
  2. Manually restore the previous secret value (or delete the slot) via the OS keychain tools.
  3. Fix the underlying backend issue, then retry `set_provider_api_key`.
  4. Treat the orphaned secret as stale and clean it up to avoid auth confusion.
Defensive patterns

Strategy: try-catch

Try / catch

match set_provider_api_key(provider, key) {
    Err(e) if e.to_string().contains("failed to restore prior secret-store state") => {
        eprintln!("Orphaned secret may remain in the keychain; restore or delete the slot manually.");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `set_provider_api_key` where `store.save()` fails and the compensating `secrets.set(slot, previous)` (or `secrets.delete(slot)`) also returns an error.

Common situations: Secret backend went read-only or disconnected between the original write and the rollback — e.g. keychain re-locked mid-operation.

Related errors


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

Appendix: source

Thrown at crates/config/src/credentials.rs:139

                "Secret storage snapshot failed for {slot}: {error}. Refusing to write the API key in plaintext to {}. Fix the configured secret backend and retry; Codewhale did not change that file.",
                crate::quote_os_path(store.path())
            ));
        }
    };
    if let Err(error) = store.save() {
        store.config = original_config;
        if secret_store_saved {
            let current = secrets
                .get(slot)
                .map_err(|rollback| anyhow::anyhow!(
                    "{error}; additionally could not verify secret-store rollback for {slot}: {rollback}"
                ))?;
            if current.as_deref() == Some(api_key) {
                match prior_secret.expect("snapshot succeeded before secret write") {
                    Some(previous) => secrets.set(slot, &previous),
                    None => secrets.delete(slot),
                }
                .map_err(|rollback| anyhow::anyhow!(
                    "{error}; additionally failed to restore prior secret-store state for {slot}: {rollback}"
                ))?;
            }
        }
        return Err(error);
    }
    crate::scrub_plaintext_api_keys_from_config_backup(store.path())
        .context("failed to scrub plaintext API keys from config backup")?;
    Ok(secret_store_saved)
}

/// What a credential clear actually accomplished.
///
/// The secret-store leg can fail after the config leg has already been
/// persisted. Reporting that separately is the point: a caller that prints
/// "cleared" while the key is still sitting in the keyring has lied about a
/// security-relevant action.
#[derive(Debug, Clone, PartialEq, Eq)]

View on GitHub (pinned to 73e0f67d83)