From 5da7dbd4c4a59dc082f059b2d024105d16baa469 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 12 Jul 2026 15:08:07 +0100 Subject: [PATCH] fix(clawhub): restore bounded promotions refresh and lint (#105411) * fix(clawhub): remove useless query body assignment * docs(agents): note delegated Testbox lifecycle * docs(agents): clarify delegated Testbox sync * fix(clawhub): bound promotions feed refresh latency --- AGENTS.md | 1 + src/infra/clawhub.promotions.test.ts | 8 ++++++++ src/infra/clawhub.ts | 6 +++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index ae93e2d88b4a..3849cf617009 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -137,6 +137,7 @@ Skills own workflows; root owns hard policy and routing. - Base/head changed: stop and rewarm Testbox; never override stale lease checks. - Compound Testbox commands: `bash -lc`, never `sh -lc`; job env uses Bash `declare`. - Testbox cleanup: `blacksmith testbox stop --id `; id is not positional. +- Delegated Testbox rejects `--fresh-pr` and `--stop-after`; sync current checkout, workflow owns lifecycle. - PR review artifacts: keep template enum values; put evidence detail in summaries. - Crabbox request means real scenario proof: install/update/call/repro user path; not just copy tests and run them remotely. - Visual proof: use Crabbox, set up like a user, then screenshot-verify. No harness/bypass/shortcut unless explicitly asked. diff --git a/src/infra/clawhub.promotions.test.ts b/src/infra/clawhub.promotions.test.ts index 3752a39c774e..6ae459b87827 100644 --- a/src/infra/clawhub.promotions.test.ts +++ b/src/infra/clawhub.promotions.test.ts @@ -190,4 +190,12 @@ describe("fetchClawHubPromotionsFeed", () => { const init = fetchImpl.mock.calls[0]?.[1] as RequestInit; expect(new Headers(init?.headers).get("if-none-match")).toBe('"seq-3"'); }); + + it("does not extend the interactive feed timeout with transient retries", async () => { + const fetchImpl = vi.fn().mockRejectedValue(new Error("offline")); + + await expect(fetchClawHubPromotionsFeed({ fetchImpl })).rejects.toThrow("offline"); + + expect(fetchImpl).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/infra/clawhub.ts b/src/infra/clawhub.ts index 0c6d3dbb3cdf..6203840102ca 100644 --- a/src/infra/clawhub.ts +++ b/src/infra/clawhub.ts @@ -424,6 +424,7 @@ type ClawHubRequestParams = { search?: Record; fetchImpl?: FetchLike; skipAuth?: boolean; + retryTransientReads?: boolean; headers?: Record; }; @@ -729,7 +730,7 @@ async function clawhubRequest( // A write may have committed before its response failed, so only replay // idempotent reads across transient ClawHub transport failures. - if ((params.method ?? "GET") !== "GET") { + if ((params.method ?? "GET") !== "GET" || params.retryTransientReads === false) { return await request(); } return await retryClawHubRead(request, { @@ -1967,6 +1968,9 @@ export async function fetchClawHubPromotionsFeed( path: "/api/v1/feeds/promotions", timeoutMs: params.timeoutMs, fetchImpl: params.fetchImpl, + // This passive refresh runs inline from interactive commands. Its cache + // cadence owns retries; shared backoff would turn the 2.5s cap into ~24s. + retryTransientReads: false, // Public CDN-served snapshot; an Authorization header would only // fragment edge caches. skipAuth: true,