fix: enforce turnstyle wait deadlines#158
Merged
Merged
Conversation
Signed-off-by: Rui Chen <rui@chenrui.dev>
chenrui333
marked this pull request as ready for review
July 15, 2026 11:27
chenrui333
marked this pull request as draft
July 15, 2026 12:39
chenrui333
marked this pull request as ready for review
July 15, 2026 12:39
Signed-off-by: Rui Chen <rui@chenrui.dev>
Signed-off-by: Rui Chen <rui@chenrui.dev>
Signed-off-by: Rui Chen <rui@chenrui.dev>
Signed-off-by: Rui Chen <rui@chenrui.dev>
Signed-off-by: Rui Chen <rui@chenrui.dev>
Signed-off-by: Rui Chen <rui@chenrui.dev>
Signed-off-by: Rui Chen <rui@chenrui.dev>
Signed-off-by: Rui Chen <rui@chenrui.dev>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
continue-after-secondsandabort-after-secondsas hard total-elapsed-time deadlines beginning before the first Actions API read.Motivation
The waiter previously counted configured sleeps instead of actual elapsed time. The initial repository workflow lookup was outside the deadline, large valid timeout values could overflow Node's timer range and fire immediately, and a delayed timer callback could allow an operation resolving at or after the boundary to win.
Octokit's private retry timers could also remain queued after the request signal was aborted. The first abortable replacement then coupled 5xx and primary-rate-limit retries through one counter: a server retry could consume the primary retry, while a primary retry could change the first server delay from one second to four. Missing or malformed reset headers could produce an endless
NaNtimer loop, and an ordinary failure from one active-status query did not cancel pending siblings. A secondary-limit response carrying primary-limit headers could also be misclassified and retried once by the custom adapter.Octokit's paginator discarded parameter-level request signals before issuing pages. After signal propagation was corrected, a final boundary remained: when monotonic time had expired but the deadline timer callback was delayed, a compound discovery callback, paginator, or retry loop could still start one more API request before the outer deadline race observed completion.
Behavior
The deadline starts after inputs are parsed and before repository workflow lookup. Covered Actions API reads receive the same lifecycle signal and monotonic checkpoint, including workflow lookup, current-run lookup, each workflow-run and repository-run status query/page, job pages, and step reads. The waiter receives the remaining portion of the original budget.
The deadline stores an absolute monotonic boundary. Each compound discovery read is raced separately, and the Octokit request hook checks the boundary before every attempt and after every fulfillment or rejection. Therefore
elapsed >= deadlineprevents another discovery read, pagination page, retry backoff, or retry request even if the scheduled deadline callback has not run.Deadline, sleep, and retry timers are scheduled in chunks no greater than
2_147_483_647milliseconds. Long valid values re-arm until the monotonic target is reached instead of overflowing. The initial discovery window begins when workflow-run discovery starts, so earlier repository workflow lookup does not consume it; its remaining sleep is still raced against the shared action deadline. The log describes that discovery window rather than promising a new full-duration sleep.Retry policy uses independent counters:
1, 4, 9...seconds based only on the server-error count;Ordinary 4xx behavior remains unchanged. Secondary-limit messages are classified before primary-limit headers and propagate after the plugin's existing warning without a custom retry. Missing, blank, non-decimal, or unsafe-integer primary reset headers propagate without retrying. Quota exhaustion is logged for every primary-limit response, while
Retrying after ...is logged only when the custom hook actually schedules its one retry.ActionDeadline.cancel()aborts outstanding work without marking the deadline elapsed.main.run()uses it before reporting ordinary failures, and standalone waiters cancel deadlines they own.dispose()remains timer-only cleanup, and successful completion does not abort its lifecycle signal.Action inputs, outputs, defaults, branch/queue matching, rerun ordering, predecessor selection, job/step fallback policy, GitHub Enterprise support, Node 24 runtime, dependencies, and esbuild bundling remain unchanged.
Tests
500 -> primary limit -> successandprimary limit -> 500 -> successmixed retry sequences.The suite now contains 9 test files and 173 tests.
Coverage
src/retry.tsremains covered at 100% for statements, branches, functions, and lines.Validation
npm ci: passed with 0 vulnerabilities.npm run typecheck: passed for source and tests.npm run test: passed, 173 tests and all coverage thresholds.npm run fmtcheck: passed.npm run build: passed.git diff --check: passed.Risk
The intentional compatibility change is timing: workflows that previously exceeded their configured maximum during workflow lookup, later API calls, a retry backoff, or a long sleep now continue or abort at the documented boundary. Ordinary terminal API failures now promptly cancel sibling work before the original error is reported.
The synchronous checkpoint is carried only as internal Octokit request metadata and is evaluated by Turnstyle's request hook; it does not change endpoint parameters or responses. Pagination results and page caps, per-lane retry counts and delays, secondary-limit no-retry behavior, queue ordering, and predecessor selection are unchanged. The lifecycle and retry adapter are exercised through deterministic scheduler tests and the real Octokit request boundary. No live API calls or secrets are used.
This does not modify PR #154's Dependabot automation or dependencies.
Generated bundle
dist/index.jswas regenerated with the existing Node 24 esbuild command. A second post-commit build produced a clean worktree, confirming deterministic regeneration.References