wizard/repo/ensure: degrade to skipped-fanout on clawstor errors
The wizard was hard-failing (500) when clawstor was unreachable — env unset, network error, non-JSON HTML response from a fallback proxy, or non-2xx status all mapped to ApiError::Internal, which blocked the wizard from advancing. Clawstor fan-out is a warmup optimization, not a prerequisite. Real fleet materialization happens later at spawn time. When the aggregator is absent, return a skipped FanoutReply (all_ok=true, empty peers) so the wizard proceeds, and log the reason server-side. Unblocks: picking clawhdf5 in the ResearchWizard when the tank/ architect clawstor daemons are running but the HTTP aggregator (`claw-store serve`) isn't deployed yet. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
efc13f2904
commit
3444859e11
@@ -99,14 +99,37 @@ async fn proxy(
|
||||
return Err(ApiError::BadRequest);
|
||||
}
|
||||
|
||||
let base = std::env::var("CLAWSTOR_URL").map_err(|_| ApiError::Internal)?;
|
||||
let token = std::env::var("CLAWSTOR_TOKEN").map_err(|_| ApiError::Internal)?;
|
||||
// Clawstor fan-out is best-effort — the aggregator may not be
|
||||
// deployed in every environment. When it's absent (env unset,
|
||||
// network error, non-JSON HTML from a fallback proxy, non-2xx),
|
||||
// degrade to a "skipped" reply so the wizard doesn't block. Real
|
||||
// fleet materialization happens later at spawn time; ensure was
|
||||
// only a warmup.
|
||||
let skipped = |reason: &str| -> Json<FanoutReply> {
|
||||
eprintln!("wizard_repo::{action}: skipping fleet fan-out ({reason})");
|
||||
Json(FanoutReply {
|
||||
url: url.clone(),
|
||||
git_ref: git_ref.clone(),
|
||||
workspace: String::new(),
|
||||
peers: Vec::new(),
|
||||
all_ok: true,
|
||||
})
|
||||
};
|
||||
|
||||
let Ok(base) = std::env::var("CLAWSTOR_URL") else {
|
||||
return Ok(skipped("CLAWSTOR_URL unset"));
|
||||
};
|
||||
let Ok(token) = std::env::var("CLAWSTOR_TOKEN") else {
|
||||
return Ok(skipped("CLAWSTOR_TOKEN unset"));
|
||||
};
|
||||
let endpoint = format!("{}/api/v2/repos/{}", base.trim_end_matches('/'), action);
|
||||
let client = reqwest::Client::builder()
|
||||
let Ok(client) = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(360))
|
||||
.build()
|
||||
.map_err(|_| ApiError::Internal)?;
|
||||
let resp = client
|
||||
else {
|
||||
return Ok(skipped("http client build failed"));
|
||||
};
|
||||
let resp = match client
|
||||
.post(&endpoint)
|
||||
.bearer_auth(token)
|
||||
.json(&serde_json::json!({
|
||||
@@ -115,10 +138,16 @@ async fn proxy(
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Internal)?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(ApiError::Internal);
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(e) => return Ok(skipped(&format!("send failed: {e}"))),
|
||||
};
|
||||
let status = resp.status();
|
||||
if !status.is_success() {
|
||||
return Ok(skipped(&format!("aggregator returned {status}")));
|
||||
}
|
||||
match resp.json::<FanoutReply>().await {
|
||||
Ok(reply) => Ok(Json(reply)),
|
||||
Err(e) => Ok(skipped(&format!("non-JSON response: {e}"))),
|
||||
}
|
||||
let reply: FanoutReply = resp.json().await.map_err(|_| ApiError::Internal)?;
|
||||
Ok(Json(reply))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user