Skip to content

fix: Stop polling an action attempt once the timeout passes - #1004

Open
razor-x wants to merge 3 commits into
mainfrom
claude/js-sdk-audit-6x115l-h3-poll-deadline
Open

fix: Stop polling an action attempt once the timeout passes#1004
razor-x wants to merge 3 commits into
mainfrom
claude/js-sdk-audit-6x115l-h3-poll-deadline

Conversation

@razor-x

@razor-x razor-x commented Aug 24, 2026

Copy link
Copy Markdown
Member

Problem

SDK audit finding H3 (high, runtime-verified): resolveActionAttempt raced an unbounded poll recursion against a timer. Promise.race rejected the caller with SeamActionAttemptTimeoutError but never cancelled the losing poll loop:

runtime: timeout 200ms → error thrown after 4 polls; 600ms later the orphan had made 18

Each timed-out wait on a pending attempt left an immortal loop polling once per interval forever, pinning the Node event loop open. Related defects in the same code: a timeout shorter than the polling interval rejected without ever polling; pollingInterval: 0 polled unthrottled for the whole window; and the timeout message read "waiting for action action attempt".

Fix

Poll iteratively against a deadline (mirroring seamapi/php#465): sleep min(pollingInterval, remaining), poll, and time out only once the deadline has actually passed — so every wait polls at least once and no orphan loop survives the error. Validate options up front with the existing SeamHttpInvalidOptionsError: negative timeout and non-positive pollingInterval are rejected. Fixed the error message typo and documented the semantics in the README.

Tests

  • after the timeout rejection, the poll request count stays frozen (fails on old code: the orphan keeps polling — the reverted run literally times out ava from the leaked 60s loop)
  • timeout: 300, pollingInterval: 60000 → exactly one poll, rejects in ~300ms (old code: zero polls)
  • timeout: -1 and pollingInterval: 0SeamHttpInvalidOptionsError with pinned messages
  • corrected timeout message pinned

Verified per the audit-notes recipe: all 4 new tests fail on the reverted source with the audit's exact symptoms. Full suite (129 tests), lint, typecheck green.

Part of applying the rev-3 SDK audit (one PR per finding). Related: #1002, #1003.

🤖 Generated with Claude Code

https://claude.ai/code/session_01B8xeJm2Hd923k8uo6eoFd2


Generated by Claude Code

Waiting for an action attempt raced an unbounded poll loop against a
timer. The caller received SeamActionAttemptTimeoutError, but the
losing poll loop was never cancelled and kept polling once per interval
forever. The race also let a timeout shorter than the polling interval
reject without ever polling, and a pollingInterval of zero polled
unthrottled for the entire timeout window.

Poll iteratively against a deadline instead: sleep the lesser of the
polling interval and the remaining budget, poll, and time out only once
the deadline has passed, so every wait polls at least once and polling
stops with the wait. Validate the options up front, raising
SeamHttpInvalidOptionsError for a negative timeout or a pollingInterval
not greater than zero, and fix the timeout error message which read
'waiting for action action attempt'.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8xeJm2Hd923k8uo6eoFd2
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8xeJm2Hd923k8uo6eoFd2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants