Skip to content

fix(gateway): disarm wrapper timeout before teardown#70704

Merged
Takhoffman merged 1 commit into
mainfrom
codex/fix-gateway-timeout-mask-race
Apr 23, 2026
Merged

fix(gateway): disarm wrapper timeout before teardown#70704
Takhoffman merged 1 commit into
mainfrom
codex/fix-gateway-timeout-mask-race

Conversation

@Takhoffman

Copy link
Copy Markdown
Contributor

The previous gateway teardown fix changed settlement ordering so one-shot CLI calls waited for stopAndWait(), but it still left the wrapper timeout armed until teardown completed. That introduced a race: if client.request() succeeded or failed with a real RPC error near timeoutMs, and teardown took slightly longer, the wrapper timer could fire first and replace the real outcome with a synthetic timeout.

Example of the bad sequence:

const result = await client.request(...); // succeeds near timeout
await stopGatewayClient(client);          // teardown still running
// wrapper timeout fires here and wins incorrectly

This follow-up fixes the ordering by claiming the outcome and clearing the wrapper timeout before awaiting teardown, while still delaying final resolve/reject until teardown completes:

const stop = (err?: Error, value?: T) => {
  if (settled) return;
  settled = true;
  clearTimeout(timer);
  void stopGatewayClient(client).finally(() => {
    if (err) reject(err);
    else resolve(value as T);
  });
};

It also adds a regression test that blocks stopAndWait(), advances fake timers past timeoutMs, and verifies a successful RPC still resolves with the real result instead of timing out.

Verified:

  • pnpm test src/gateway/call.test.ts
  • staged repo checks from the commit hook, including check:changed, typechecks, lint, import-cycle guards, and gateway tests

@aisle-research-bot

aisle-research-bot Bot commented Apr 23, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Unhandled promise rejection risk during gateway client teardown (possible process crash/DoS)
1. 🟡 Unhandled promise rejection risk during gateway client teardown (possible process crash/DoS)
Property Value
Severity Medium
CWE CWE-248
Location src/gateway/call.ts:487-499

Description

executeGatewayRequestWithScopes triggers gateway client teardown but intentionally ignores the returned promise:

  • stop() calls void stopGatewayClient(client).finally(...).
  • If stopGatewayClient() ever rejects/throws (e.g., unexpected exceptions from client.stopAndWait() or client.stop() / beginStop()), the rejection from the .finally(...) chain is unhandled because there is no .catch() and the promise is not awaited.
  • In Node.js, unhandled promise rejections can terminate the process depending on runtime/version/config (--unhandled-rejections=strict), enabling a denial-of-service scenario.

Vulnerable code:

void stopGatewayClient(client).finally(() => {
  if (err) {
    reject(err);
  } else {
    resolve(value as T);
  }
});

Recommendation

Ensure teardown failures cannot surface as unhandled promise rejections.

Option A (recommended): explicitly catch and swallow/log teardown errors:

void stopGatewayClient(client)
  .catch((e) => {// optionally log debug-level; teardown errors should not crash the process// logDebug(`stopGatewayClient failed: ${String(e)}`);
  })
  .finally(() => {
    if (err) reject(err);
    else resolve(value as T);
  });

Option B: make stopGatewayClient never reject by wrapping all internal calls with try/catch (including client.stop()), returning void reliably.


Analyzed PR: #70704 at commit 6463a0b

Last updated on: 2026-04-23T17:29:32Z

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S maintainer Maintainer-authored PR labels Apr 23, 2026
@greptile-apps

greptile-apps Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a race condition in executeGatewayRequestWithScopes where the wrapper setTimeout could fire after a successful RPC if stopGatewayClient took longer than the remaining timeout budget. The fix consolidates settled = true, clearTimeout(timer), and the stopGatewayClient call inside stop(), so the timer is disarmed synchronously before any async teardown begins. The regression test validates this by blocking stopAndWait(), advancing fake timers past timeoutMs, and confirming the promise still resolves with the real RPC result.

Confidence Score: 5/5

This PR is safe to merge — it correctly resolves the race and the regression test clearly exercises the fixed path.

The change is small and surgical. The stop() closure correctly sets settled = true and clears the timer synchronously before awaiting teardown, which is precisely the fix needed. Call sites that previously awaited teardown before calling stop are simplified correctly. The .finally() callback runs even if stopGatewayClient rejects, so resolve/reject are always reached. No P0 or P1 findings.

No files require special attention.

Reviews (1): Last reviewed commit: "fix(gateway): disarm wrapper timeout bef..." | Re-trigger Greptile

@Takhoffman
Takhoffman merged commit d39e34d into main Apr 23, 2026
69 of 72 checks passed
@Takhoffman
Takhoffman deleted the codex/fix-gateway-timeout-mask-race branch April 23, 2026 17:36
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant