Server-execution websocket loop drops session-level failures with request_id: null, turning a definite engine error into a 600s hang and a misleading timeout
Context: API Makeathon participant. Found while reviewing the server-execution path; independently confirmed by a second review pass.
Under server-side KCL execution, the follow-up command loop filters every response by request id before handling failures, so a session-level failure that arrives with request_id: null never matches and is silently dropped. The CLI then waits out the full response timeout and blames a timeout, hiding the real error the engine already sent.
Evidence
src/context.rs, run_server_kcl_then_modeling_cmds: the follow-up loop (around lines 545-563) does if response_request_id(&resp) != Some(cmd_id) { continue; } before it can act on a failure. FailureWebSocketResponse.request_id is Option<Uuid> and is documented as null when no request id was sent (connection-level errors, auth/entitlement failures, engine-side aborts). The response timeout is WS_RESPONSE_TIMEOUT_SECS = 600 (src/context.rs:31).
The execution loop directly above (around lines 483-530) shows the intended behavior: it bails on any WebSocketResponse::Failure regardless of request_id. The two loops are inconsistent; the second swallows exactly the class of error the first surfaces.
A related amplifier: the read helper (around src/context.rs:1554-1577) creates a fresh response cap on every call and returns after any decoded response, so heartbeat Pong frames and unrelated session-data responses reset the cap and keep the future pending rather than letting a request-level deadline fire.
Concrete failure
An account gets blocked, or the engine pool dies, between KCL execution and the follow-up Volume/Export command under server execution. The CLI sits for 10 minutes and then prints engine websocket response timed out after 600s, even though the engine already reported a definite failure. Any downstream tool that classifies CLI output sees a transport-looking timeout instead of the real engine error.
Verify
Unit-testable without the API: feed the follow-up loop a WebSocketResponse::Failure { request_id: None, errors: [sentinel] } followed by nothing. The current code discards the sentinel and hangs; the execution loop handles the same message.
Suggested fix
Handle WebSocketResponse::Failure with request_id == None || request_id == Some(cmd_id) before the id filter, mirroring the execution loop, so a session-level failure surfaces immediately.
Environment
Zoo CLI v0.2.184 (33534cd). Reviewed against the current main of KittyCAD/cli.
Server-execution websocket loop drops session-level failures with
request_id: null, turning a definite engine error into a 600s hang and a misleading timeoutContext: API Makeathon participant. Found while reviewing the server-execution path; independently confirmed by a second review pass.
Under server-side KCL execution, the follow-up command loop filters every response by request id before handling failures, so a session-level failure that arrives with
request_id: nullnever matches and is silently dropped. The CLI then waits out the full response timeout and blames a timeout, hiding the real error the engine already sent.Evidence
src/context.rs,run_server_kcl_then_modeling_cmds: the follow-up loop (around lines 545-563) doesif response_request_id(&resp) != Some(cmd_id) { continue; }before it can act on a failure.FailureWebSocketResponse.request_idisOption<Uuid>and is documented as null when no request id was sent (connection-level errors, auth/entitlement failures, engine-side aborts). The response timeout isWS_RESPONSE_TIMEOUT_SECS = 600(src/context.rs:31).The execution loop directly above (around lines 483-530) shows the intended behavior: it bails on any
WebSocketResponse::Failureregardless ofrequest_id. The two loops are inconsistent; the second swallows exactly the class of error the first surfaces.A related amplifier: the read helper (around
src/context.rs:1554-1577) creates a fresh response cap on every call and returns after any decoded response, so heartbeat Pong frames and unrelated session-data responses reset the cap and keep the future pending rather than letting a request-level deadline fire.Concrete failure
An account gets blocked, or the engine pool dies, between KCL execution and the follow-up
Volume/Exportcommand under server execution. The CLI sits for 10 minutes and then printsengine websocket response timed out after 600s, even though the engine already reported a definite failure. Any downstream tool that classifies CLI output sees a transport-looking timeout instead of the real engine error.Verify
Unit-testable without the API: feed the follow-up loop a
WebSocketResponse::Failure { request_id: None, errors: [sentinel] }followed by nothing. The current code discards the sentinel and hangs; the execution loop handles the same message.Suggested fix
Handle
WebSocketResponse::Failurewithrequest_id == None || request_id == Some(cmd_id)before the id filter, mirroring the execution loop, so a session-level failure surfaces immediately.Environment
Zoo CLI v0.2.184 (33534cd). Reviewed against the current
mainof KittyCAD/cli.