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,