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);
|
return Err(ApiError::BadRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
let base = std::env::var("CLAWSTOR_URL").map_err(|_| ApiError::Internal)?;
|
// Clawstor fan-out is best-effort — the aggregator may not be
|
||||||
let token = std::env::var("CLAWSTOR_TOKEN").map_err(|_| ApiError::Internal)?;
|
// 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 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))
|
.timeout(std::time::Duration::from_secs(360))
|
||||||
.build()
|
.build()
|
||||||
.map_err(|_| ApiError::Internal)?;
|
else {
|
||||||
let resp = client
|
return Ok(skipped("http client build failed"));
|
||||||
|
};
|
||||||
|
let resp = match client
|
||||||
.post(&endpoint)
|
.post(&endpoint)
|
||||||
.bearer_auth(token)
|
.bearer_auth(token)
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
@@ -115,10 +138,16 @@ async fn proxy(
|
|||||||
}))
|
}))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| ApiError::Internal)?;
|
{
|
||||||
if !resp.status().is_success() {
|
Ok(r) => r,
|
||||||
return Err(ApiError::Internal);
|
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