From bbfb65e9892386463ee362ad3f4846e2eb23e051 Mon Sep 17 00:00:00 2001 From: gok03 Date: Tue, 19 May 2026 20:49:18 +0530 Subject: [PATCH 1/2] =?UTF-8?q?fix(client):=20bump=20HTTP=20timeout=201.5s?= =?UTF-8?q?=20=E2=86=92=205s,=20allow=20REFUSE=5FTIMEOUT=5FMS=20override?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hardcoded 1.5s HTTP timeout was too tight for real-world networks: hotel Wi-Fi, long-RTT links, or any path where TLS handshake + first byte stretches past 1.5s would see every gate call fail open with a "server: unreachable" line in stderr. Bump the default to 5s (still a clearly-interactive feel — a user typing `pip install foo` won't notice an extra ~half-second on a slow network, but will appreciate not seeing the gate fail) and expose a REFUSE_TIMEOUT_MS env var for users who want to tune it further. README documents both the new env var and REFUSE_NO_GATE (which already existed in shim/run.go but wasn't documented). --- README.md | 2 ++ internal/server/client.go | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f6c6659..c5e650f 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,8 @@ Environment variables override the file (useful in CI): - `REFUSE_POLICY` — sets `severity_threshold` - `REFUSE_FAIL_CLOSED` — `1`/`true` to enable - `REFUSE_ALLOW_VULNERABLE` — `1`/`true` to bypass a single install +- `REFUSE_TIMEOUT_MS` — HTTP timeout in milliseconds (default `5000`) +- `REFUSE_NO_GATE` — `1` to skip the gate entirely for the next call (debug) ## Pointing at a server diff --git a/internal/server/client.go b/internal/server/client.go index 3445858..0661e3c 100644 --- a/internal/server/client.go +++ b/internal/server/client.go @@ -8,6 +8,8 @@ import ( "fmt" "io" "net/http" + "os" + "strconv" "time" ) @@ -18,13 +20,25 @@ type Client struct { HTTP *http.Client } -// New returns a Client with sensible defaults (1.5 s timeout — install gates -// run interactively and shouldn't wait long). +// defaultTimeout balances "user is waiting at a terminal" against +// "transient network jitter / TLS handshake cold-start." 1.5s was too +// tight in practice — every laptop on hotel Wi-Fi or a long-RTT link +// fell over. +const defaultTimeout = 5 * time.Second + +// New returns a Client with sensible defaults. The HTTP timeout can be +// overridden by setting REFUSE_TIMEOUT_MS=. func New(baseURL, apiKey string) *Client { + timeout := defaultTimeout + if v := os.Getenv("REFUSE_TIMEOUT_MS"); v != "" { + if ms, err := strconv.Atoi(v); err == nil && ms > 0 { + timeout = time.Duration(ms) * time.Millisecond + } + } return &Client{ BaseURL: baseURL, APIKey: apiKey, - HTTP: &http.Client{Timeout: 1500 * time.Millisecond}, + HTTP: &http.Client{Timeout: timeout}, } } From 73b37875924f8683a5da54e92191c2e57a3e86a8 Mon Sep 17 00:00:00 2001 From: gok03 Date: Thu, 21 May 2026 09:20:45 +0530 Subject: [PATCH 2/2] fix(client): default timeout 8s to absorb hosted server cold start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured mcp.refuse.dev latency: first request after idle is ~4.6s (scale-from-zero cold start), then 0.2–0.4s warm. A 5s default left only ~0.4s margin over the cold start; bump to 8s so the first install after an idle period doesn't fail open with a "server: unreachable" warning. Still fails reasonably fast when the server is genuinely down, and REFUSE_TIMEOUT_MS overrides for anyone who wants tighter/looser. --- README.md | 2 +- internal/server/client.go | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c5e650f..bdc1f15 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Environment variables override the file (useful in CI): - `REFUSE_POLICY` — sets `severity_threshold` - `REFUSE_FAIL_CLOSED` — `1`/`true` to enable - `REFUSE_ALLOW_VULNERABLE` — `1`/`true` to bypass a single install -- `REFUSE_TIMEOUT_MS` — HTTP timeout in milliseconds (default `5000`) +- `REFUSE_TIMEOUT_MS` — HTTP timeout in milliseconds (default `8000`) - `REFUSE_NO_GATE` — `1` to skip the gate entirely for the next call (debug) ## Pointing at a server diff --git a/internal/server/client.go b/internal/server/client.go index 0661e3c..7ad6382 100644 --- a/internal/server/client.go +++ b/internal/server/client.go @@ -20,11 +20,14 @@ type Client struct { HTTP *http.Client } -// defaultTimeout balances "user is waiting at a terminal" against -// "transient network jitter / TLS handshake cold-start." 1.5s was too -// tight in practice — every laptop on hotel Wi-Fi or a long-RTT link -// fell over. -const defaultTimeout = 5 * time.Second +// defaultTimeout balances "user is waiting at a terminal" against the +// realities of the hosted server. mcp.refuse.dev scales to zero and +// takes ~4.6s to cold-start; the original 1.5s timeout meant the first +// install after an idle period always failed open with a scary +// "server: unreachable" line. 8s absorbs a cold start with margin while +// still failing reasonably fast when the server is genuinely down. +// Tunable via REFUSE_TIMEOUT_MS. +const defaultTimeout = 8 * time.Second // New returns a Client with sensible defaults. The HTTP timeout can be // overridden by setting REFUSE_TIMEOUT_MS=.