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
- Do not call set on entries from this store; go through the gix credentials cascade/prompt flow the store implements (get/delete)
- 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
- 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
- Treat the cascade-backed store as read/delete-only; writes belong to the credential helper prompt flow
- Wrap store capability checks behind one helper instead of scattering trait calls
- Add tests that only cover get/delete so CI never exercises the unreachable arm
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
- broker has not been configured
- BUG: Sensitive data cannot be serialized - it needs to be ex
- askpass broker must be initialized
- broker already configured
- BUG: {id} is queued
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/cedf422725c4a044.
Report an issue: GitHub.