jdx/mise · warning · eyre::Report

remote task action manifest changed too frequently

Error message

remote task action manifest changed too frequently

What it means

put_remote_task_manifest uploads the merged manifest with an If-Match ETag precondition. On PreconditionFailed it re-fetches the remote manifest, merges, and retries — but the loop runs only 4 iterations (for _ in 0..4). If another writer updates the remote manifest on every attempt, the CAS loop gives up and bails.

Source

Thrown at crates/mise-cache-core/src/agent.rs:671

                let _permit = self.remote_transfers.acquire().await?;
                remote
                    .put_action_manifest(&selector, &bytes, expected_etag.as_deref())
                    .await?
            };
            match outcome {
                ManifestPutOutcome::Stored => return Ok(manifest),
                ManifestPutOutcome::PreconditionFailed => {
                    let Some((remote_manifest, etag)) = self.get_remote_task_manifest(task).await?
                    else {
                        expected_etag = None;
                        continue;
                    };
                    manifest = merge_task_manifests(task, Some(remote_manifest), manifest)?;
                    expected_etag = Some(etag);
                }
            }
        }
        bail!("remote task action manifest changed too frequently")
    }

    /// Return a snapshot of this session's cache activity.
    pub fn stats(&self) -> AgentStats {
        AgentStats {
            session_duration_ns: 0,
            lookups: self.stats.lookups.load(Ordering::Relaxed),
            hits: self.stats.hits.load(Ordering::Relaxed),
            stores: self.stats.stores.load(Ordering::Relaxed),
            stored_bytes: self.stats.stored_bytes.load(Ordering::Relaxed),
            verifications: self.stats.verifications.load(Ordering::Relaxed),
            divergences: self.stats.divergences.load(Ordering::Relaxed),
            downloaded_bytes: self.stats.downloaded_bytes.load(Ordering::Relaxed),
            uploaded_bytes: self.stats.uploaded_bytes.load(Ordering::Relaxed),
            prefetched_actions: self.stats.prefetched_actions.load(Ordering::Relaxed),
            remote_manifest_lookups: self.stats.remote_manifest_lookups.load(Ordering::Relaxed),
            remote_manifest_lookup_duration_ns: self
                .stats

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Retry the whole commit after a short backoff — begin_task/commit again; contention is usually transient
  2. Stagger concurrent task completions for the same task identity (queue or jitter the writers)
  3. If it persists, identify the client continuously rewriting that manifest entry (buggy writer) and stop it
Defensive patterns

Strategy: retry

Try / catch

let mut backoff = Duration::from_millis(500);
loop {
    match agent.commit_task(&run).await {
        Ok(_) => break,
        Err(e) if e.to_string().contains("changed too frequently") && attempt < 3 => {
            tokio::time::sleep(backoff).await;
            backoff *= 2;
            run = agent.begin_task(&task).await?; // fresh baseline + etag
        }
        Err(e) => return Err(e),
    }
}

Prevention

When it happens

Trigger: Four or more concurrent sessions committing manifests for the same 64-hex task identity, each invalidating the others' ETags between get and put; a writer stuck in a rapid update loop on the same manifest key.

Common situations: A CI fleet fanning out many jobs for the same task finishing simultaneously against one shared remote cache; a misbehaving client rewriting the manifest entry in a tight loop.

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/a0a04257de39cd26. Report an issue: GitHub.