Skip to content

Commit f761914

Browse files
author
sergey-ermakovich
committed
Retry the live tool call when the free plan's concurrency limit trips
The CI key runs on the free plan, where concurrency is 1. Pushing several of these repos at once made their contract runs collide, and HasData answers 429 with code concurrency_limit immediately rather than queueing, which turned a plan limit into a red badge. The rpc helper now retries such a response up to five times with a growing pause. A 401 still fails on the first attempt.
1 parent 964f55f commit f761914

1 file changed

Lines changed: 26 additions & 15 deletions

File tree

test/tools.test.mjs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,32 @@ function parseRpc(raw, id) {
5252
let nextId = 1;
5353

5454
async function rpc(method, params = {}) {
55-
const id = nextId++;
56-
const res = await fetch(ENDPOINT, {
57-
method: 'POST',
58-
headers: {
59-
'x-api-key': KEY,
60-
'Content-Type': 'application/json',
61-
// The server answers over streamable HTTP, so accept both a plain body and a stream.
62-
Accept: 'application/json, text/event-stream',
63-
},
64-
body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
65-
signal: AbortSignal.timeout(TIMEOUT_MS),
66-
});
67-
assert.equal(res.status, 200, `${method} returned ${res.status}`);
68-
const raw = await res.text();
69-
return { raw, body: parseRpc(raw, id) };
55+
// The CI key sits on the free plan, where concurrency is 1. When several of
56+
// these repos are pushed at once their contract runs collide, and HasData
57+
// answers 429 with code concurrency_limit straight away rather than queueing.
58+
// That is a plan limit, not a broken contract, so the call is retried before
59+
// the test gives up. A 401 still fails on the first attempt.
60+
for (let attempt = 1; ; attempt++) {
61+
const id = nextId++;
62+
const res = await fetch(ENDPOINT, {
63+
method: 'POST',
64+
headers: {
65+
'x-api-key': KEY,
66+
'Content-Type': 'application/json',
67+
// The server answers over streamable HTTP, so accept both a plain body and a stream.
68+
Accept: 'application/json, text/event-stream',
69+
},
70+
body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
71+
signal: AbortSignal.timeout(TIMEOUT_MS),
72+
});
73+
assert.equal(res.status, 200, `${method} returned ${res.status}`);
74+
const raw = await res.text();
75+
if (raw.includes('concurrency_limit') && attempt < 5) {
76+
await new Promise((r) => setTimeout(r, attempt * 4000));
77+
continue;
78+
}
79+
return { raw, body: parseRpc(raw, id) };
80+
}
7081
}
7182

7283
// One network round trip for every test that needs the list.

0 commit comments

Comments
 (0)