From ec8eb58458a0ed54b3cfab131e9bd8a67056fb23 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 7 Oct 2026 00:24:27 +1300 Subject: [PATCH 1/5] perf(build): keep only line tables in dev builds Every agent worktree shares one cargo build dir, which reached 89-130 GB in a day or two. Full debuginfo in dev and test builds was a fifth of each artifact set while backtraces only need line tables, and dependency debuginfo is never read outside a debugger. Measured on a clean `cargo build --workspace --exclude zone_desktop --all-targets`, then three edits to zone_server: full debuginfo 155 s clean, 32 GB -> 36 GB, edit-rebuild 33-36 s line tables only 123 s clean, 25 GB -> 29 GB, edit-rebuild 32-36 s Incremental stays on: turning it off for tests saved another 9 GB but doubled the edit-rebuild to 66-70 s. CI already sets CARGO_PROFILE_{DEV,TEST}_DEBUG=0, which still wins over the manifest, and the release profile is unchanged. Co-Authored-By: Claude Opus 5.5 --- runner/Cargo.toml | 6 ++++++ runner/README.md | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/runner/Cargo.toml b/runner/Cargo.toml index 5b4444a8..dabcff2f 100644 --- a/runner/Cargo.toml +++ b/runner/Cargo.toml @@ -68,3 +68,9 @@ features = ["signal", "process"] lto = true codegen-units = 1 strip = true + +[profile.dev] +debug = "line-tables-only" + +[profile.dev.package."*"] +debug = false diff --git a/runner/README.md b/runner/README.md index 6bdbb43b..6da74783 100644 --- a/runner/README.md +++ b/runner/README.md @@ -121,6 +121,16 @@ cargo test cargo run --bin zone-server ``` +### Debug Info + +Dev and test builds carry line tables for workspace crates and no debuginfo +for dependencies, so backtraces keep file and line while the build cache stays +small. Full debuginfo for workspace crates, when a debugger needs variables: + +```bash +CARGO_PROFILE_DEV_DEBUG=full cargo build -p zone_server +``` + ### sqlx Offline Query Cache `runner/.cargo/config.toml` sets `SQLX_OFFLINE=true`, so every `sqlx::query!` From 4d373ef3878a19ce1a9ebff1c8703b13a9322abd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 7 Oct 2026 00:41:17 +1300 Subject: [PATCH 2/5] fix(chat): box the chat route so routing passes clippy Route grew past 856 bytes, so every Routing value carried it and clippy's large_enum_variant has failed the lint job on main since 2026-10-05. Co-Authored-By: Claude Opus 5.5 --- runner/zone_server/src/ws/chat.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/runner/zone_server/src/ws/chat.rs b/runner/zone_server/src/ws/chat.rs index 70cc1d5d..065bc649 100644 --- a/runner/zone_server/src/ws/chat.rs +++ b/runner/zone_server/src/ws/chat.rs @@ -388,7 +388,7 @@ enum Routing { Video(crate::config::ComfyUiConfig), Audio(crate::config::ComfyUiConfig), Upscale(crate::config::ComfyUiConfig), - Chat(chats::ChatRow, Route), + Chat(chats::ChatRow, Box), } /// What a provider reported when a turn failed, remedied for a coding agent's @@ -2473,6 +2473,7 @@ async fn handle_send_message( .await } Routing::Chat(mut chat, route) => { + let route = *route; // Cleared before the prompt is built rather than after it. // `prepare_chat` renders the approval rules from this flag, so // setting it on the preparation instead would gate the tools @@ -2666,7 +2667,9 @@ async fn prepare_message( } crate::services::image_intent::GenerationIntent::Audio => Routing::Audio(image_config), crate::services::image_intent::GenerationIntent::Upscale => Routing::Upscale(image_config), - crate::services::image_intent::GenerationIntent::Chat => Routing::Chat(chat, route), + crate::services::image_intent::GenerationIntent::Chat => { + Routing::Chat(chat, Box::new(route)) + } }) } @@ -4569,7 +4572,7 @@ mod tests { QUESTION, None, chat, - route, + *route, false, ) .await From 00cfbd5ad9280496a6bfde9cc93a9a404286209b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 7 Oct 2026 00:47:50 +1300 Subject: [PATCH 3/5] test(server): point unreachable services at a port nobody can take Binding port 0 and dropping the listener frees that port, and with nextest running tests in parallel another test's mock server took it: the upscale test got a 404 from wiremock instead of a refused connection. A privileged port is never handed out by bind(0), so 127.0.0.1:1 is refused every time. Holding the port bound without listening was not an option, because macOS drops the SYN and the connection times out instead. Co-Authored-By: Claude Opus 5.5 --- runner/zone_server/tests/chat_ws_tests.rs | 35 ++++++++++++++------ runner/zone_server/tests/common/mod.rs | 4 +++ runner/zone_server/tests/lora_flows_tests.rs | 13 ++------ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/runner/zone_server/tests/chat_ws_tests.rs b/runner/zone_server/tests/chat_ws_tests.rs index 2c582c08..3d96f173 100644 --- a/runner/zone_server/tests/chat_ws_tests.rs +++ b/runner/zone_server/tests/chat_ws_tests.rs @@ -12,8 +12,8 @@ mod common; use base64::Engine; use common::{ - TestClient, create_test_pool, create_test_router, create_test_state, init_tracing, test_config, - test_email, test_password, + REFUSED_URL, TestClient, create_test_pool, create_test_router, create_test_state, init_tracing, + test_config, test_email, test_password, }; use futures_util::{SinkExt, StreamExt}; use serde_json::{Value, json}; @@ -1973,16 +1973,32 @@ async fn test_upscale_output_too_large_says_so_rather_than_claiming_nothing_came let _ = tokio::fs::remove_dir_all(artifact_root).await; } +#[tokio::test] +async fn the_refused_url_refuses_connections_on_a_port_never_handed_out() { + let address: std::net::SocketAddr = REFUSED_URL.trim_start_matches("http://").parse().unwrap(); + let connection = tokio::time::timeout( + Duration::from_secs(5), + tokio::net::TcpStream::connect(address), + ) + .await + .expect("a refused connection fails at once rather than timing out"); + assert_eq!( + connection.map(|_| ()).unwrap_err().kind(), + std::io::ErrorKind::ConnectionRefused + ); + assert!( + address.port() < 1024, + "binding port 0 hands out only unprivileged ports, so no parallel test can take this one" + ); +} + #[tokio::test] async fn test_upscale_reports_an_unreachable_comfyui_without_an_empty_message() { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let refused = listener.local_addr().unwrap(); - drop(listener); let artifact_root = std::env::temp_dir().join(format!("zone-ws-upscale-refused-{}", uuid::Uuid::new_v4())); let mut config = test_config(); config.comfyui.enabled = true; - config.comfyui.base_url = format!("http://{refused}"); + config.comfyui.base_url = REFUSED_URL.to_string(); config.comfyui.poll_interval_ms = 50; config.comfyui.artifact_root = artifact_root.clone(); let (_client, _token, _chat_id, mut socket) = image_socket(config).await; @@ -2629,7 +2645,7 @@ async fn test_image_status_precedes_stalled_prompt_and_timeout_is_visible() { config.comfyui.enabled = true; config.comfyui.base_url = comfy.uri(); config.comfyui.request_timeout_secs = 1; - config.litellm_host = "http://127.0.0.1:9".to_string(); + config.litellm_host = REFUSED_URL.to_string(); let (_, _, _, mut socket) = image_socket(config).await; socket.send(WsMessage::Text(json!({"type":"send", "content":"Generate an image of the same rooster facing the other way"}).to_string().into())).await.unwrap(); let mut status = false; @@ -2660,12 +2676,9 @@ async fn test_image_status_precedes_stalled_prompt_and_timeout_is_visible() { #[tokio::test] async fn test_image_refused_connection_reports_error_without_empty_message() { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let address = listener.local_addr().unwrap(); - drop(listener); let mut config = test_config(); config.comfyui.enabled = true; - config.comfyui.base_url = format!("http://{address}"); + config.comfyui.base_url = REFUSED_URL.to_string(); let (client, token, chat_id, mut socket) = image_socket(config).await; socket .send(WsMessage::Text( diff --git a/runner/zone_server/tests/common/mod.rs b/runner/zone_server/tests/common/mod.rs index 6a0fa70e..bb4dd7dc 100644 --- a/runner/zone_server/tests/common/mod.rs +++ b/runner/zone_server/tests/common/mod.rs @@ -28,6 +28,10 @@ use zone_server::config::Config; use zone_server::routes::create_router; use zone_server::state::AppState; +/// Refuses every connection. A privileged port: binding port 0 never hands it +/// out, so unlike a freed ephemeral port no parallel test's server can take it. +pub const REFUSED_URL: &str = "http://127.0.0.1:1"; + /// Send server tracing to the test's stdout. Without this the server's own /// logs vanish and a failing integration test says nothing about why. pub fn init_tracing() { diff --git a/runner/zone_server/tests/lora_flows_tests.rs b/runner/zone_server/tests/lora_flows_tests.rs index 861640b9..af5398f3 100644 --- a/runner/zone_server/tests/lora_flows_tests.rs +++ b/runner/zone_server/tests/lora_flows_tests.rs @@ -234,13 +234,6 @@ async fn mock_ollama() -> String { format!("http://{addr}") } -async fn dead_ollama() -> String { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - drop(listener); - format!("http://{addr}") -} - fn plant_image_bases(models_dir: &std::path::Path) { fs::write( models_dir.join("checkpoints/flux1-schnell-fp8.safetensors"), @@ -524,7 +517,7 @@ async fn comfy_pull_writes_lora_from_hub_origin() { axum::serve(listener, hub).await.unwrap(); }); - let mut config = common::test_config_with_ollama_host("http://127.0.0.1:9"); + let mut config = common::test_config_with_ollama_host(common::REFUSED_URL); config.huggingface_models_url = format!("http://{hub_addr}/api/models"); config.comfyui.models_dir = models_dir.clone(); let pool = common::create_test_pool().await; @@ -1476,9 +1469,9 @@ async fn train_bases_lists_chat_models_alongside_image_rows() { async fn train_bases_keep_image_rows_when_ollama_is_down() { let models_dir = temp_models(); plant_image_bases(&models_dir); - let ollama = dead_ollama().await; let catalog = start_catalog(split_catalog).await; - let (router, token) = router_with(&ollama, &catalog, models_dir.clone(), None).await; + let (router, token) = + router_with(common::REFUSED_URL, &catalog, models_dir.clone(), None).await; let (status, body) = get_json(router, &token, "/api/models/train/bases").await; assert_eq!(status, StatusCode::OK, "{body}"); let bases = body.as_array().expect("bases list"); From 31dbceb9d0a3ee8ab273fa45bbcce30e54381938 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 7 Oct 2026 11:47:18 +1300 Subject: [PATCH 4/5] fix(login): read a pending codex sign-in before the logins it may record A status read listed the organization's logins, refreshed their usage, and only then looked for the sign-in in flight. A sign-in that ended in between recorded its login and dropped its attempt, so the read found neither and reported signed out. CI caught it as a flaky agent_login_tests run. A sign-in records its login before it drops its attempt, so reading the attempt first means a read that finds no attempt finds the login. Co-Authored-By: Claude Opus 5.5 --- .../zone_server/src/services/login/status.rs | 21 +++++---- runner/zone_server/tests/agent_login_tests.rs | 43 ++++++++++++++++++- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/runner/zone_server/src/services/login/status.rs b/runner/zone_server/src/services/login/status.rs index b1f4fbb3..a33c40b8 100644 --- a/runner/zone_server/src/services/login/status.rs +++ b/runner/zone_server/src/services/login/status.rs @@ -80,14 +80,8 @@ impl AgentStatus { viewer: Viewer, attempt: Option, ) -> Result { - let logins = agent_logins::list_for(state.db(), organization, agent.as_str()).await?; - let snapshots = refreshed(state, &logins).await; - let now = Utc::now(); - let readings: Vec = logins - .iter() - .zip(snapshots) - .map(|(login, snapshot)| read(state, login, snapshot, logins.len(), now)) - .collect(); + // The sign-in in flight is read before the logins: a codex sign-in records its login + // before it drops its attempt, so a read that finds no attempt finds the login. let mut status = Self::signed_out(agent); let pending = match agent { AgentKind::Claude => { @@ -97,10 +91,19 @@ impl AgentStatus { None } AgentKind::Codex => { + let pending = devices::pending(organization); status.error = devices::failure(organization); - devices::pending(organization) + pending } }; + let logins = agent_logins::list_for(state.db(), organization, agent.as_str()).await?; + let snapshots = refreshed(state, &logins).await; + let now = Utc::now(); + let readings: Vec = logins + .iter() + .zip(snapshots) + .map(|(login, snapshot)| read(state, login, snapshot, logins.len(), now)) + .collect(); let best = readings .iter() diff --git a/runner/zone_server/tests/agent_login_tests.rs b/runner/zone_server/tests/agent_login_tests.rs index 17f4d27c..cd7b14a0 100644 --- a/runner/zone_server/tests/agent_login_tests.rs +++ b/runner/zone_server/tests/agent_login_tests.rs @@ -457,13 +457,23 @@ impl Stage { /// Codex runs from `executable`, with its homes under a private state root. async fn codex(executable: &Path) -> Self { + Self::codex_reading_usage( + executable, + MockServer::start().await, + AgentConfig::default().usage_ttl, + ) + .await + } + + /// A codex server that reads its logins' usage from `api` once a reading is `ttl` old. + async fn codex_reading_usage(executable: &Path, api: MockServer, ttl: Duration) -> Self { let state = TempDir::new().expect("an agent state root"); - let api = MockServer::start().await; let config = Config { model_backend: codex_at(executable.to_path_buf()), agents: AgentConfig { state: state.path().join("agents"), host_login: false, + usage_ttl: ttl, claude_api_url: api.uri(), codex_api_url: api.uri(), ..AgentConfig::default() @@ -3511,6 +3521,37 @@ async fn a_member_reads_every_login_and_never_a_token() { assert_eq!(stage.status(organization, "claude", &member).await, *claude); } +#[tokio::test] +async fn a_status_read_while_a_codex_sign_in_ends_never_misses_the_login_it_saved() { + const SLOW: Duration = Duration::from_secs(3); + let api = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/backend-api/wham/usage")) + .respond_with(ResponseTemplate::new(503).set_delay(SLOW)) + .mount(&api) + .await; + let codex = Codex::new(); + let stage = Stage::codex_reading_usage(&codex.executable, api, Duration::ZERO).await; + let owner = person(&stage.client).await; + let organization = organization(&stage.client, &owner).await; + stage.sign_in_codex(organization, &codex, &owner).await; + fs::remove_file(codex.marker(Codex::APPROVE)).expect("the first approval withdrawn"); + codex.saves(OTHER_ACCOUNT); + stage.start(organization, "codex", json!({}), &owner).await; + + let (status, ()) = tokio::join!(stage.status(organization, "codex", &owner), async { + tokio::time::sleep(SLOW / 6).await; + codex.approve(); + }); + + let logins = status["logins"].as_array().map_or(0, Vec::len); + assert!( + status["state"] == "pending" || logins == 2, + "a status that is no longer pending left out the login that ended it: {status}" + ); + stage.sign_out(organization, "codex", &owner).await; +} + #[tokio::test] async fn cancelling_a_codex_device_sign_in_keeps_the_logins_already_held() { let codex = Codex::new(); From 0e3b2f11a89965cedcefbeac9cb37a830e05a2db Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 7 Oct 2026 11:55:31 +1300 Subject: [PATCH 5/5] fix(chat): drop the second unboxing of the chat route Main boxed Routing::Chat's route the same way and unboxes it at the prepare_chat call, so the merge unboxed it twice and the server stopped compiling. Keep main's version. Co-Authored-By: Claude Opus 5.5 --- runner/zone_server/src/ws/chat.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/runner/zone_server/src/ws/chat.rs b/runner/zone_server/src/ws/chat.rs index d5ef1c89..df188670 100644 --- a/runner/zone_server/src/ws/chat.rs +++ b/runner/zone_server/src/ws/chat.rs @@ -2473,7 +2473,6 @@ async fn handle_send_message( .await } Routing::Chat(mut chat, route) => { - let route = *route; // Cleared before the prompt is built rather than after it. // `prepare_chat` renders the approval rules from this flag, so // setting it on the preparation instead would gate the tools