jlcodes99/cockpit-tools · error
网关未在超时时间内返回唤醒结果,最后状态={}
Error message
网关未在超时时间内返回唤醒结果,最后状态={} What it means
trigger_wakeup_via_client_gateway_once sends a wakeup request to the client gateway, then polls the cascade trajectory for a plannerResponse.modifiedResponse entry within a fixed timeout window. If the poll loop exhausts without seeing a result (and without any trajectory error), it returns Err with '网关未在超时时间内返回唤醒结果,最后状态={}'. The {} carries the last observed cascade status (empty means no trajectory was ever seen). It is a timeout/no-result guard, not a network failure per se: the gateway stayed reachable but never produced the planner's final modified response in time.
Source
Thrown at src-tauri/src/modules/wakeup.rs:1634
}
if let Some(err) = last_running_error {
crate::modules::logger::log_error(&format!(
"[Wakeup] 网关轨迹持续错误且未恢复: cascade_id={}, last_status={}, error_code={:?}, message={}",
cascade_id,
if last_status.is_empty() { "-" } else { &last_status },
err.error_code,
truncate_log_text(&err.message, 500)
));
return Err(encode_wakeup_ui_error_payload(&err));
}
let message = if last_status.is_empty() {
"网关未返回唤醒结果(轨迹中未出现 plannerResponse.modifiedResponse)".to_string()
} else {
format!("网关未在超时时间内返回唤醒结果,最后状态={}", last_status)
};
crate::modules::logger::log_error(&format!(
"[Wakeup] 网关唤醒失败(超时/无结果): cascade_id={}, error={}",
cascade_id, message
));
Err(message)
}
.await;
if matches!(&wakeup_result, Err(err) if is_wakeup_cancelled_message(err)) {
let cleanup_client = client.clone();
let cleanup_url = format!("{}/DeleteCascadeTrajectory", service_base);
let cleanup_cascade_id = cascade_id.clone();
tokio::spawn(async move {
let delete_resp = cleanup_client
.post(cleanup_url)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.json(&json!({ "cascadeId": cleanup_cascade_id }))
.send()
.await;View on GitHub (pinned to 1ed8b77992)
Solutions
- Check the gateway cascade status in the message tail: if still RUNNING, increase the poll timeout/interval budget or retry the wakeup once, since the run may simply need more time.
- If last_status is empty, verify the cascade was actually created and the GetCascadeTrajectory endpoint/params (cascadeId) are correct before assuming a planner problem.
- Inspect the raw gateway trajectory logs for plannerResponse shape; if the field moved or was renamed after a gateway upgrade, update extract_wakeup_response_from_gateway_trajectory's extraction keys.
- Look upstream for why the planner hung (auth validation_url, tool hang, rate limits) — this error is downstream of the cascade never finishing.
- If the trajectory kept erroring while RUNNING, rely on the last_running_error path instead; consider surfacing the trajectory error code to the user rather than a generic timeout.
Example fix
// before: fixed budget poll loop
while poll_idx < MAX_POLLS { poll().await?; poll_idx += 1; }
Err(format!("网关未在超时时间内返回唤醒结果,最后状态={}", last_status))
// after: budget-aware retry + actionable status in message
if is_cascade_status_running(&last_status) {
log_warn("wakeup timed out while RUNNING; retrying once");
return trigger_wakeup_via_client_gateway_once(...).await; // retry
}
Err(format!("网关唤醒超时: cascade_id={}, last_status={}, 建议: 检查网关/认证后重试", cascade_id, if last_status.is_empty() { "UNKNOWN" } else { &last_status })) Defensive patterns
Strategy: retry
Validate before calling
// Before calling, ensure gateway reachability and a sane budget
let health = reqwest::Client::new()
.get(format!("{}/health", service_base))
.timeout(Duration::from_secs(5))
.send().await;
if health.is_err() || !health.unwrap().status().is_success() {
return Err("gateway unreachable; fix connectivity before wakeup".into());
} Try / catch
match trigger_wakeup_via_client_gateway(...).await {
Err(msg) if msg.contains("超时时间内返回唤醒结果") => {
// check last_status; retry once with a longer budget if cascade was RUNNING
log_warn(&format!("wakeup timeout, retrying: {}", msg));
trigger_wakeup_via_client_gateway(...).await
}
other => other,
} Prevention
- Set a poll budget that reflects realistic planner latency (measure P95 duration from successful wakeups) instead of a fixed guess.
- Verify the cascade actually reaches a terminal status by watching last_status in logs; alert on cascades stuck RUNNING.
- After gateway upgrades, smoke-test that plannerResponse.modifiedResponse still appears in trajectories and update the extractor keys.
- Check upstream auth (validation_url) health before initiating wakeup, since auth hangs are a top cause of non-terminating cascades.
When it happens
Trigger: trigger_wakeup_via_client_gateway -> trigger_wakeup_via_client_gateway_once starts a cascade, polls GetCascadeTrajectory every CLIENT_GATEWAY_POLL_INTERVAL_MS, and the loop completes all poll attempts without extract_wakeup_response_from_gateway_trajectory ever matching plannerResponse.modifiedResponse and without extract_gateway_error_from_trajectory returning an error. Also produced when the cascade stays stuck in a non-terminal last_status (e.g. RUNNING forever) or last_status is empty because the trajectory never materialized.
Common situations: Gateway/planner backend is slow or overloaded so the wakeup exceeds the poll budget; the model never emits a plannerResponse (agent hangs waiting on a tool or auth); proxy or gateway version change renames/moves the modifiedResponse field so extraction silently fails; cancel token fired late so polling ended before result; trajectory creation succeeded but the cascade never started (last_status empty).
Related errors
- TimedOut
- TimedOut
- 刷新 Codex 配额失败
- [App] Silent update download failed, retrying (${retryIndex}
- [WS] 接收错误 {}: {}
AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05).
Data as JSON: /api/errors/9086fb738f9fa198.
Report an issue: GitHub.