Skip to content

Commit fc8d205

Browse files
committed
fix: clamp pollDeviceAuth sleep + request timeout to remaining budget
1 parent fbd7843 commit fc8d205

2 files changed

Lines changed: 16 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
dangling timeouts.
1616
- Device-auth poll requests now carry a per-request `AbortController`
1717
(10s) so a hung HTTP call can no longer outlive the overall 30s
18-
`POLL_TIMEOUT_MS` budget.
18+
`POLL_TIMEOUT_MS` budget. Sleep interval and request timeout are
19+
both clamped to the remaining budget at each iteration, so
20+
`pollDeviceAuth()` honors its advertised deadline even when a
21+
fetch is started late in the cycle.
1922
- Expired plugin-managed auth tokens now fall through to the file-based
2023
auto re-auth path (Path B) instead of returning the "update your
2124
openclaw.json" message. `runShellSecurityFlow` inspects the raw

src/auth/device-auth.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,21 @@ export async function pollDeviceAuth(
123123
const deadline = Date.now() + POLL_TIMEOUT_MS;
124124

125125
while (Date.now() < deadline) {
126-
await sleep(POLL_INTERVAL_MS);
126+
// Clamp sleep to remaining budget so we don't oversleep past the
127+
// deadline and then start yet another fetch.
128+
const sleepMs = Math.min(POLL_INTERVAL_MS, deadline - Date.now());
129+
if (sleepMs > 0) await sleep(sleepMs);
130+
// Same rationale for the per-request timeout: without this clamp,
131+
// a fetch started near the end of the budget could run for the
132+
// full POLL_REQUEST_TIMEOUT_MS and push us past the advertised
133+
// overall deadline. Skip the iteration entirely when the remaining
134+
// budget is zero or negative.
135+
const remaining = deadline - Date.now();
136+
if (remaining <= 0) break;
127137
const controller = new AbortController();
128138
const requestTimeout = setTimeout(
129139
() => controller.abort(),
130-
POLL_REQUEST_TIMEOUT_MS,
140+
Math.min(POLL_REQUEST_TIMEOUT_MS, remaining),
131141
);
132142
try {
133143
const resp = await fetchFn(pollUrl, { signal: controller.signal });

0 commit comments

Comments
 (0)