Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4edcddc
fix: land the timeout-audit review fixes for the merged #63
asto18089 Sep 27, 2026
e8e78c9
fix(snapshot): refuse truncated git captures in the restore and histo…
asto18089 Sep 30, 2026
1aa27e1
fix(tools): treat the drain size cap as truncation evidence on the pl…
asto18089 Sep 30, 2026
839ed58
fix(app-server): write the consumed seq back when a turn ends in stre…
asto18089 Sep 30, 2026
46ab4fa
fix(tasks): lead the init-failure remedy with the stale-lock sweep
asto18089 Sep 30, 2026
6f220ab
fix(runtime): log a dropped forced-approval publish instead of swallo…
asto18089 Sep 30, 2026
46b2589
fix(tasks): skip liveness heartbeats in the trailing drain loop
asto18089 Sep 30, 2026
11c1b13
refactor(client): keep the injectable envelope seam private
asto18089 Sep 30, 2026
5bb5aad
docs(mcp): disclose the engine-side stdio proxy's unbounded send leg …
asto18089 Sep 30, 2026
78bc601
docs: qualify the budget and approval-contract claims, sync zh_hans
asto18089 Sep 30, 2026
1dc13a8
fix(tools): anchor drain truncation evidence to the capture tail
asto18089 Sep 30, 2026
49f811d
fix(runtime): log dropped approval resolutions instead of swallowing …
asto18089 Sep 30, 2026
78f0d84
docs(runtime): align the settle deny comment with live-receiver settl…
asto18089 Sep 30, 2026
727326b
fix(tasks): name the cost of removing a half-initialized snapshot repo
asto18089 Sep 30, 2026
664ab32
docs(tasks): disclose the debounce flush starvation shape in code
asto18089 Sep 30, 2026
8ebe94c
docs(subagents): correct the omitted-vs-zero max_steps fallback
asto18089 Sep 30, 2026
67b838c
docs(runtime): state the required-emit failure outcome without pinnin…
asto18089 Sep 30, 2026
263b968
docs(mcp): disclose the unbounded stdio send leg in the budget contract
asto18089 Sep 30, 2026
4b5415b
fix(tasks): keep a timed-out gate's exit code absent
asto18089 Oct 2, 2026
a9423dd
fix(snapshot): sweep stale ref-update locks on open
asto18089 Oct 2, 2026
4bba570
fix(client): keep isolated anthropic requests off shared health
asto18089 Oct 2, 2026
e8a3075
fix(runtime): guard the approval rollback and settlement log
asto18089 Oct 2, 2026
b0df7ff
fix(tools): fall through the group-kill ESRCH arm to the child kill
asto18089 Oct 2, 2026
8c5043e
fix(tools): bound the echoed stdout in the plugin truncation error
asto18089 Oct 2, 2026
b6d2672
docs(snapshot): align the paths module doc with the bounded probe
asto18089 Oct 2, 2026
ea07845
docs(mcp): scope the unbounded send leg to the engine-side proxy
asto18089 Oct 2, 2026
891004b
fix(snapshot): sweep a stale HEAD.lock on the open path
asto18089 Oct 2, 2026
3c649d5
fix(client): gate the anthropic status arm off isolated requests
asto18089 Oct 2, 2026
e620466
fix(tasks): name ref locks in the generic git timeout hint
asto18089 Oct 2, 2026
379b950
docs(tasks): correct the heartbeat starvation disclosure
asto18089 Oct 2, 2026
b8de056
docs(tools): reword the group-kill error rationale
asto18089 Oct 2, 2026
0362c2c
docs: tighten the posture and proxy timeout wording
asto18089 Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/app-server/src/chat_completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,13 @@ pub(crate) async fn chat_completions_handler(
// Build upstream request. The shared platform builder sets no timeouts,
// so the proxy would hang forever on an accept-and-stall upstream;
// bound both the connect and the whole non-streaming round trip.
//
// `config` (the read guard) is dropped here on purpose: everything it
// feeds is resolved by now, and holding it across the upstream round
// trip (up to UPSTREAM_TOTAL_TIMEOUT) would block config writes and,
// through the write-preferring lock, every later resolve for that
// whole window.
drop(config);
let upstream_req = codewhale_release::platform_http_client_builder()
.connect_timeout(UPSTREAM_CONNECT_TIMEOUT)
.timeout(UPSTREAM_TOTAL_TIMEOUT)
Expand Down
595 changes: 558 additions & 37 deletions crates/app-server/src/lib.rs

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions crates/cli/src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ const GITHUB_RELEASE_DOWNLOAD_BASE_URL: &str =
const UPDATE_HTTP_ATTEMPTS: usize = 3;
const UPDATE_HTTP_RETRY_DELAY_MS: u64 = 100;
/// Ceiling for one asset download. Release binaries are tens of megabytes
/// and some of the networks this exists for are slow: 600s covers a full
/// 60 MiB at ~100 KiB/s (the same download budget the audit gives skill
/// tarballs; the old 300s needed an implausible >1.6 Mbps to finish).
/// and some of the networks this exists for are slow: 600s covers ~58 MiB
/// at ~100 KiB/s (the same download budget the audit gives skill tarballs;
/// the old 300s needed an implausible >1.6 Mbps to finish).
const UPDATE_DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(600);
/// Ceiling for one checksum-manifest probe. The manifest is a few hundred
/// bytes, so this is only a backstop against a source that accepts the
Expand Down
14 changes: 13 additions & 1 deletion crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,19 @@ use uuid::Uuid;
/// Per-tool dispatch budget for the headless runtime. 30 minutes: tools
/// legitimately run long (builds, test suites, MCP-backed calls), and this
/// wrapper is a runaway backstop, not an expected duration — the previous
/// 300s value cut off healthy in-flight tool work.
/// 300s value cut off healthy in-flight tool work. Part of the 1800s family
/// (TUI client envelope, vision request envelope, model stream cap
/// `STREAM_MAX_DURATION_SECS`, dynamic-tool result wait, sub-agent tool
/// timeout, MCP execute timeout, the mirrors in app-server and the MCP stdio
/// proxy, the background-task wall clock `TaskExecutionLimits::wall_time`,
/// the sub-agent `default_wall_time_secs`, and the fleet `builder` role
/// preset) that comments keep in sync; this comment anchors the family
/// roster — there is no shared constant across the crates yet.
///
/// Several family members are configurable defaults rather than constants
/// (`STREAM_MAX_DURATION_SECS`, the MCP `execute_timeout`, and the sub-agent
/// `default_wall_time_secs`): a family-wide bump changes their defaults, not
/// their ceilings, and user overrides survive it.
fn tool_dispatch_timeout() -> Duration {
if cfg!(test) {
Duration::from_millis(50)
Expand Down
26 changes: 26 additions & 0 deletions crates/mcp/src/stdio_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,12 @@ struct Connection {

impl Connection {
fn send(&mut self, message: &Value) -> Result<()> {
// Known unbounded leg (deliberate, disclosed debt): the write below
// is blocking std I/O while the connection mutex is held, so a
// server that never drains its stdin can park this leg past every
// budget — unlike the read leg, which `request_with_timeout`
// bounds. Fixing it needs an async or threaded writer and is
// tracked as follow-up scale, not silently assumed safe.
let stdin = self
.stdin
.as_ref()
Expand Down Expand Up @@ -899,6 +905,26 @@ impl Drop for Connection {
}
}

#[cfg(test)]
mod budget_pins {
// The engine-side stdio proxy has no behavioral timeout tests (the
// budgets are compile-time constants), so pin the wiring values: a
// drift here silently re-bounds every MCP `tools/call` the engine
// proxy carries. CALL_TOOL_TIMEOUT must mirror the TUI pool's default
// execute timeout (1800s), and the generic request budget must stay
// separate and much shorter.
use super::{CALL_TOOL_TIMEOUT, HANDSHAKE_TIMEOUT, REQUEST_TIMEOUT};
use std::time::Duration;

#[test]
fn call_tool_budget_mirrors_the_pool_default_and_stays_separate() {
assert_eq!(CALL_TOOL_TIMEOUT, Duration::from_secs(1800));
assert_eq!(REQUEST_TIMEOUT, Duration::from_secs(120));
assert!(CALL_TOOL_TIMEOUT > REQUEST_TIMEOUT);
assert_eq!(HANDSHAKE_TIMEOUT, Duration::from_secs(30));
}
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;
Expand Down
Loading
Loading