Skip to content

vendor: actions/checkout@v6.0.2 with timeout-minutes input#129

Open
tenstorrent-github-bot wants to merge 5 commits into
mainfrom
brain/checkout-with-timeout
Open

vendor: actions/checkout@v6.0.2 with timeout-minutes input#129
tenstorrent-github-bot wants to merge 5 commits into
mainfrom
brain/checkout-with-timeout

Conversation

@tenstorrent-github-bot

Copy link
Copy Markdown

Summary

Vendors actions/checkout@de0fac2e (v6.0.2) under .github/actions/checkout/ and adds a timeout-minutes input (default: 20 min) that hard-kills hung git subprocesses.

Why vendor?

timeout-minutes on steps inside a composite action is silently ignored by the GitHub runner (actions/runner#1979). The only way to enforce a wall-clock limit on git fetch/clone calls is to do it inside the action's own TypeScript code.

Why not Promise.race?

Promise.race rejects the outer promise when the timer fires but the underlying child_process spawned by @actions/exec keeps running in the OS — useless as a hung-fetch guard. This implementation uses child_process.spawn directly, sends SIGTERM on timeout, and escalates to SIGKILL after 5 s.

Commits

Commit 1: verbatim copy of actions/checkout@de0fac2e — no modifications, easy to diff against upstream
Commit 2: adds timeout-minutes input wired through IGitSourceSettingsGitCommandManager.execGitWithTimeout

Usage

- uses: tenstorrent/tt-github-actions/.github/actions/checkout@<sha>
  with:
    timeout-minutes: 30   # default: 20

BrAIn added 2 commits May 31, 2026 03:55
Source: https://github.com/actions/checkout/tree/de0fac2e4500dabe0009e67214ff5f5447ce83dd

No modifications — this commit is a clean copy of upstream to make
the following diff easy to review.
Adds a `timeout-minutes` input (default: 20) that imposes a hard wall-clock
limit on every git subprocess. When the timeout fires the child process is
sent SIGTERM; if it is still alive 5 s later it receives SIGKILL.

This is implemented via `child_process.spawn` rather than `Promise.race`
over `@actions/exec`. Promise.race rejects the outer promise on timeout but
leaves the underlying OS process running — making it useless as a guard
against a hung git fetch.

Changed files:
  action.yml                  — new timeout-minutes input (default: 20)
  src/git-source-settings.ts  — new timeoutMs field on IGitSourceSettings
  src/input-helper.ts         — parse and validate timeout-minutes
  src/git-source-provider.ts  — pass timeoutMs to createCommandManager
  src/git-command-manager.ts  — store timeoutMs; execGitWithTimeout uses
                                child_process.spawn + SIGTERM/SIGKILL
  dist/index.js               — rebuilt
@tenstorrent-github-bot tenstorrent-github-bot requested a review from a team as a code owner May 31, 2026 03:58

@tenstorrent-github-bot tenstorrent-github-bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of commit 2: timeout-minutes input

Overall this is a solid feature addition. The core approach — bypass @actions/exec and use child_process.spawn directly to get timeout control — is reasonable since @actions/exec doesn't expose timeout support.

However, there are two real bugs that should be fixed before merging, plus a couple of robustness improvements worth considering.

Bugs (must fix)

  1. Inner SIGKILL timer leaks — the 5-second escalation timer inside the timeout callback is never cleared when the process exits. This keeps the Node event loop alive and fires kill() on a dead PID.
  2. Infinity bypasses validationparseFloat('Infinity') passes the > 0 check, and setTimeout(fn, Infinity) coerces to 0 in Node.js, making every git command time out immediately.

Robustness (nice to have)

  1. Partial-line delivery for stdline/errline — splitting on \n per data event doesn't guarantee line-aligned chunks. The original @actions/exec uses internal line buffering. Acceptable for now since stdline/errline are only used for progress output, but worth a comment.
  2. Windows: SIGTERM is a no-op for non-Node processeschild.kill('SIGTERM') does nothing to git.exe on Windows. The process only dies when the SIGKILL fires 5s later. Minor since GHA runners are mostly Linux, but the 5s delay is unnecessary on Windows.

Comment thread .github/actions/checkout/src/git-command-manager.ts
Comment thread .github/actions/checkout/src/git-command-manager.ts
Comment thread .github/actions/checkout/src/input-helper.ts Outdated
Comment thread .github/actions/checkout/src/git-command-manager.ts
Comment thread .github/actions/checkout/src/git-command-manager.ts
Comment thread .github/actions/checkout/src/git-command-manager.ts
- Store inner SIGKILL timer and clear it in close/error handlers to
  prevent event loop leaks and stale kill calls
- Reject Infinity in timeout-minutes validation (!isFinite check)
- Improve timeout error message to show both seconds and minutes
- Add NOTE comment about partial-line delivery limitation in stdline

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@tenstorrent-github-bot tenstorrent-github-bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of commit 3 (2b94e280) — Follow-up Pass

All items from the first review have been addressed. The killTimer cleanup, !isFinite() guard, improved error message, and partial-line comment are all good.

After a thorough second pass, I found one real bug and one minor inconsistency. Everything else checks out.

Verified Correct

  • Timer race conditions: killTimer is assigned inside the outer timer callback. JavaScript's single-threaded event loop guarantees the entire callback (including the killTimer = setTimeout(...) assignment) runs to completion before any I/O event (like close) can fire. So killTimer is always assigned before close can check it. ✅
  • timedOut flag race: Same event loop guarantee — either clearTimeout(timer) in close cancels the outer timer before it fires, or the outer timer fires first and sets timedOut = true. Both paths are deterministic. No race. ✅
  • !isFinite(timeoutMinutes) validation: Correctly blocks Infinity and -Infinity (which pass !isNaN and > 0 checks). ✅
  • dist/index.js sync: Verified the compiled JS matches the TS source for all changes (killTimer, isFinite, error message, comment). ✅
  • env type cast: env as NodeJS.ProcessEnv is correct — both are {[key: string]: string | undefined}. ✅
  • action.yml default vs input-helper.ts fallback: Both use '20'. The || '20' in TS is redundant since GHA injects the YAML default, but it's a fine defensive pattern and not worth removing. ✅
  • spawn stdio: cp.spawn defaults to stdio: 'pipe' for all three streams, so child.stdout and child.stderr are always Readable streams. No null-check needed. ✅

Summary

One real issue (child.killed semantics), one nit (missing errline comment). Neither is a merge blocker — the child.killed issue is pre-existing from upstream actions/checkout, and git processes do respond to SIGTERM on Linux. But worth noting for awareness.

LGTM — good to merge. 🧠

Comment thread .github/actions/checkout/src/git-command-manager.ts Outdated
Comment thread .github/actions/checkout/src/git-command-manager.ts
BrAIn and others added 2 commits May 31, 2026 04:40
…e partial-line note

- Replace `!child.killed` guard with `process.kill(pid, 0)` to correctly
  detect whether the process is still alive before escalating to SIGKILL.
  `child.killed` only reflects whether `.kill()` was called, not whether
  the process actually terminated.
- Add partial-line limitation NOTE comment to the errline handler,
  matching the existing comment on the stdline handler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Upstream actions/checkout has trailing whitespace and missing end-of-file
newlines that conflict with our pre-commit rules. Exclude the entire
vendored directory to keep it as close to upstream as possible.

@vmilosevic vmilosevic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this change?

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