gitbutlerapp/gitbutler · error

unused

Error message

unused

What it means

but-secret implements a custom keyring CredentialStore that drives git's credential cascade (gix::credentials helper) for get/delete flows. `set_secret` is never invoked by keyring in that flow, so it is wired to `unreachable!("unused")`. Calling it means code tried to programmatically write a credential into a store that only supports prompt-based get and cascade-driven delete (crates/but-secret/src/secret.rs:256).

Source

Thrown at crates/but-secret/src/secret.rs:256

                .store
                .credentials(&self.handle, None)
                .map_err(|err| keyring::Error::PlatformFailure(err.into()))?;
            match cascade.invoke(get_action, prompt) {
                Ok(Some(out)) => Ok(out.identity.password),
                Ok(None) => Err(keyring::Error::NoEntry),
                Err(err) => {
                    tracing::debug!(err = ?err, "credential-helper invoke failed - usually this means it wanted to prompt which is disabled");
                    Err(keyring::Error::NoEntry)
                }
            }
        }

        fn as_any(&self) -> &dyn Any {
            self
        }

        fn set_secret(&self, _password: &[u8]) -> keyring::Result<()> {
            unreachable!("unused")
        }

        fn get_secret(&self) -> keyring::Result<Vec<u8>> {
            unreachable!("unused")
        }

        #[instrument(skip(self), err(Debug))]
        fn delete_credential(&self) -> keyring::Result<()> {
            let (mut cascade, action, prompt) = self
                .store
                .credentials(&self.handle, None)
                .map_err(|err| keyring::Error::PlatformFailure(err.into()))?;
            let ctx = action.context().expect("available for get").to_owned();
            let action = gix::credentials::helper::NextAction::from(ctx).erase();
            cascade
                .invoke(action, prompt)
                .map_err(|err| keyring::Error::PlatformFailure(err.into()))?;
            Ok(())

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Do not call set on entries from this store; go through the gix credentials cascade/prompt flow the store implements (get/delete)
  2. If write support is genuinely needed, implement set_secret to invoke the cascade with `gix::credentials::helper::NextAction::from(ctx).store(...)` instead of unreachable, mirroring delete_credential
  3. For tests, only exercise the supported surface (get/delete)
Defensive patterns

Strategy: validation

Validate before calling

// Only exercise the supported surface: get/delete via the cascade store
let entry = but_secret::entry(&store, service, user)?;
// supported:
let cred = entry.get_credential();
entry.delete_credential();
// NOT supported by this store - would panic:
// entry.set_password(...);

Type guard

// keyring::CredentialStore exposes as_any; use it to detect stores lacking set support
fn supports_set(store: &dyn keyring::CredentialStore) -> bool {
    store.as_any().downcast_ref::<CascadeStore>().is_none()
}

Prevention

When it happens

Trigger: Invoking `keyring::Entry::set_password`/`set_secret` on an Entry backed by this cascade store - e.g. new code that persists a token instead of letting the credential helper prompt, or a test harness that exercises every trait method.

Common situations: Adding a login/pat-save feature that writes to the keyring directly; porting code from a different keyring backend that did support set; test suites iterating over store capabilities.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/cedf422725c4a044. Report an issue: GitHub.