fix(#2175): return 409/400 not 500 for turn-in-flight + validation errors
This commit is contained in:
parent
cea2f2faff
commit
c708f698ee
1 changed files with 54 additions and 18 deletions
|
|
@ -531,11 +531,29 @@ async fn api_loose_ends(State(state): State<AppState>) -> Response {
|
|||
{
|
||||
Ok(Ok(hive_sh4re::Response::LooseEnds { loose_ends })) => loose_ends,
|
||||
Ok(Ok(hive_sh4re::Response::Err { message })) => {
|
||||
return error_response(&format!("get_loose_ends: {message}"));
|
||||
return error_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("get_loose_ends: {message}"),
|
||||
);
|
||||
}
|
||||
Ok(Ok(other)) => {
|
||||
return error_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("unexpected response: {other:?}"),
|
||||
);
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
return error_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("transport: {e:#}"),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
return error_response(
|
||||
StatusCode::CONFLICT,
|
||||
"get_loose_ends: timed out — hive-c0re busy, retry",
|
||||
);
|
||||
}
|
||||
Ok(Ok(other)) => return error_response(&format!("unexpected response: {other:?}")),
|
||||
Ok(Err(e)) => return error_response(&format!("transport: {e:#}")),
|
||||
Err(_) => return error_response("get_loose_ends: timed out — hive-c0re busy, retry"),
|
||||
};
|
||||
axum::Json(serde_json::json!({ "loose_ends": loose_ends })).into_response()
|
||||
}
|
||||
|
|
@ -877,7 +895,7 @@ struct SendForm {
|
|||
async fn post_send(State(state): State<AppState>, Form(form): Form<SendForm>) -> Response {
|
||||
let body = form.body.trim().to_owned();
|
||||
if body.is_empty() {
|
||||
return error_response("send: `body` required");
|
||||
return error_response(StatusCode::BAD_REQUEST, "send: `body` required");
|
||||
}
|
||||
let result = match tokio::time::timeout(
|
||||
SOCKET_FETCH_TIMEOUT,
|
||||
|
|
@ -902,7 +920,10 @@ async fn post_send(State(state): State<AppState>, Form(form): Form<SendForm>) ->
|
|||
// inbox row gets consumed by the time `TurnEnd` fires the
|
||||
// existing turn-end refresh.
|
||||
Ok(()) => (axum::http::StatusCode::OK, "ok").into_response(),
|
||||
Err(e) => error_response(&format!("send failed: {e}")),
|
||||
Err(e) => error_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("send failed: {e}"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -984,7 +1005,10 @@ async fn post_login_start(State(state): State<AppState>) -> Response {
|
|||
state.bus.emit_status("needs_login_in_progress");
|
||||
(axum::http::StatusCode::OK, "ok").into_response()
|
||||
}
|
||||
Err(e) => error_response(&format!("login start failed: {e:#}")),
|
||||
Err(e) => error_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("login start failed: {e:#}"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -996,10 +1020,13 @@ struct CodeForm {
|
|||
async fn post_login_code(State(state): State<AppState>, Form(form): Form<CodeForm>) -> Response {
|
||||
let session = state.session.lock().unwrap().clone();
|
||||
let Some(session) = session else {
|
||||
return error_response("no login session running");
|
||||
return error_response(StatusCode::CONFLICT, "no login session running");
|
||||
};
|
||||
if let Err(e) = session.submit_code(&form.code).await {
|
||||
return error_response(&format!("submit code failed: {e:#}"));
|
||||
return error_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("submit code failed: {e:#}"),
|
||||
);
|
||||
}
|
||||
(axum::http::StatusCode::OK, "ok").into_response()
|
||||
}
|
||||
|
|
@ -1035,7 +1062,7 @@ struct ModelForm {
|
|||
async fn post_set_model(State(state): State<AppState>, Form(form): Form<ModelForm>) -> Response {
|
||||
let name = form.model.trim();
|
||||
if name.is_empty() {
|
||||
return error_response("model: name required");
|
||||
return error_response(StatusCode::BAD_REQUEST, "model: name required");
|
||||
}
|
||||
state.bus.set_model(name);
|
||||
state.bus.emit(crate::events::LiveEvent::Note {
|
||||
|
|
@ -1059,10 +1086,13 @@ struct EffortForm {
|
|||
async fn post_set_effort(State(state): State<AppState>, Form(form): Form<EffortForm>) -> Response {
|
||||
let level = form.effort.trim();
|
||||
if !crate::events::is_valid_effort(level) {
|
||||
return error_response(&format!(
|
||||
"effort: level must be one of {}",
|
||||
crate::events::EFFORT_LEVELS.join(", ")
|
||||
));
|
||||
return error_response(
|
||||
StatusCode::BAD_REQUEST,
|
||||
&format!(
|
||||
"effort: level must be one of {}",
|
||||
crate::events::EFFORT_LEVELS.join(", ")
|
||||
),
|
||||
);
|
||||
}
|
||||
state.bus.set_effort(level);
|
||||
state.bus.emit(crate::events::LiveEvent::Note {
|
||||
|
|
@ -1079,7 +1109,10 @@ async fn post_compact(State(state): State<AppState>) -> Response {
|
|||
// Reject immediately if a normal turn is in flight — concurrent access
|
||||
// to the claude session is unsafe and produces garbled output.
|
||||
let Ok(guard) = lock.try_lock_owned() else {
|
||||
return error_response("turn in flight — wait for it to finish before compacting");
|
||||
return error_response(
|
||||
StatusCode::CONFLICT,
|
||||
"turn in flight — wait for it to finish before compacting",
|
||||
);
|
||||
};
|
||||
let bus = state.bus.clone();
|
||||
let files = state.files.clone();
|
||||
|
|
@ -1211,10 +1244,13 @@ async fn post_cancel_turn(State(state): State<AppState>) -> Response {
|
|||
(axum::http::StatusCode::OK, "ok").into_response()
|
||||
}
|
||||
|
||||
fn error_response(message: &str) -> Response {
|
||||
fn error_response(status: StatusCode, message: &str) -> Response {
|
||||
// Plain text — JS app surfaces in `alert()`, HTML wrapping would just
|
||||
// be noise.
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, message.to_owned()).into_response()
|
||||
// be noise. Status is per-caller: 400 for bad input, 409 for a
|
||||
// retryable state conflict (turn in flight / hive-c0re busy), 500 only
|
||||
// for a genuine server/transport failure — the frontend shows the code
|
||||
// in its alert, so a benign "busy, retry" must not read as a 500.
|
||||
(status, message.to_owned()).into_response()
|
||||
}
|
||||
|
||||
/// Read `HIVE_AVAILABLE_MODELS` (comma-separated short names injected by
|
||||
|
|
|
|||
Loading…
Reference in a new issue