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

  1. 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.
  2. If last_status is empty, verify the cascade was actually created and the GetCascadeTrajectory endpoint/params (cascadeId) are correct before assuming a planner problem.
  3. 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.
  4. Look upstream for why the planner hung (auth validation_url, tool hang, rate limits) — this error is downstream of the cascade never finishing.
  5. 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

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


AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05). Data as JSON: /api/errors/9086fb738f9fa198. Report an issue: GitHub.