GitoxideLabs/gitoxide · error
user error: multiple calls are allowed only until it…
Error message
user error: multiple calls are allowed only until it succeeds
What it means
This `.expect("user error: multiple calls are allowed only until it succeeds")` fires when `PrepareFetch::fetch_only` (gix clone/fetch) is invoked more than once, or after the prepare state was already consumed by a successful fetch. Internally the builder holds `repo` in an `Option` that is taken on success; a second call finds `None` and the process panics. The message explicitly frames this as a user error: the API permits exactly one fetch attempt.
Solutions
- Call `fetch_only`/`fetch_then_checkout` exactly once per `PrepareFetch`; create a new `PrepareFetch` for retries.
- Use the returned values immediately; don't stash the prepare handle for later reuse.
- Restructure control flow so success paths never fall through to a second fetch call.
- Match on the first call's `Option` result explicitly instead of re-invoking.
Example fix
// before let prep = gix::clone::PrepareFetch::new(...)?; let _ = prep.fetch_only(PackCache, Interrupts)?; let _ = prep.fetch_only(PackCache, Interrupts)?; // panics // after let prep = gix::clone::PrepareFetch::new(...)?; let remote = prep.fetch_only(PackCache, Interrupts)?; // single call // for retries, rebuild: // let prep = gix::clone::PrepareFetch::new(...)?;
Defensive patterns
Strategy: validation
Validate before calling
// PrepareFetch cannot be queried directly; track consumption yourself
if fetch_already_attempted {
anyhow::bail!("PrepareFetch::fetch_only must be called at most once; rebuild the prepare state to retry");
} Try / catch
let result = std::panic::catch_unwind(AssertUnwindSafe(|| prep.fetch_only(cache, interrupts)));
match result {
Ok(inner) => inner?,
Err(_) => anyhow::bail!("fetch prepare state already consumed; create a new PrepareFetch"),
} Prevention
- Call fetch_only/fetch_then_checkout exactly once per PrepareFetch
- Rebuild PrepareFetch for each retry attempt
- Never reuse a prepare handle after a successful fetch
- Structure code so success paths cannot re-enter fetch calls
When it happens
Trigger: Calling `fetch_only()` (or `fetch_then_checkout()`) twice on the same `PrepareFetch` value, or calling `fetch_only` after `fetch_then_checkout` already succeeded and consumed the state.
Common situations: Retry loops that re-call `fetch_only` on the same prepared handle after a failure consumed intermediate state or after success; conditional code paths that accidentally invoke fetch twice.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- no item index implies having an object id
- ' ' is not a valid configuration key
- Cannot use iter_v1() on index of type
- Cannot use iter_v2() on index of type
- BUG: tries to obtain object id from symbolic target
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/96cc02c1770d07c8.
Report an issue: GitHub.
Appendix: source
Thrown at gix/src/clone/fetch/mod.rs:120
///
/// Even though `async` is technically supported, it will still be blocking in nature as it uses a lot of non-async writes
/// and computation under the hood. Thus it should be spawned into a runtime which can handle blocking futures.
#[gix_protocol::bisync::bisync]
pub async fn fetch_only<P>(
&mut self,
mut progress: P,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> Result<(crate::Repository, crate::remote::fetch::Outcome), Error>
where
P: crate::NestedProgress,
P::SubProgress: 'static,
{
use crate::{bstr::ByteVec, remote, remote::fetch::RefLogMessage};
let mut repo = self
.repo
.as_ref()
.expect("user error: multiple calls are allowed only until it succeeds")
.clone();
repo.committer_or_set_generic_fallback()?;
if !self.config_overrides.is_empty() {
let mut snapshot = repo.config_snapshot_mut();
snapshot.append_config(&self.config_overrides, gix_config::Source::Api)?;
}
let remote_name = match self.remote_name.as_ref() {
Some(name) => name.to_owned(),
None => repo
.config
.resolved
.string(crate::config::tree::Clone::DEFAULT_REMOTE_NAME)
.map(|n| crate::config::tree::Clone::DEFAULT_REMOTE_NAME.try_into_symbolic_name(n))
.transpose()?
.unwrap_or_else(|| {View on GitHub (pinned to e73179060b)