Chore(ci): add job-level timeout-minutes to the remaining five workflows - #403
Open
AmaadMartin wants to merge 1 commit into
Open
Chore(ci): add job-level timeout-minutes to the remaining five workflows#403AmaadMartin wants to merge 1 commit into
AmaadMartin wants to merge 1 commit into
Conversation
Every job in these five workflows inherits GitHub's 360-minute default job timeout. The cross-language job on macos-latest makes three uncached network fetches (npm install and two go mod tidy runs); a hung fetch burns up to six hours of runner time with no signal on the PR. The other four are seconds-long github-script/bash jobs that have no legitimate reason to run for hours either. Each value is sized from that workflow's own observed job execution time (completed_at - started_at from the jobs API), not from run-level wall clock, which is inflated by runner queue wait that is not charged against timeout-minutes: cross-language-integration run-tests max 143s -> 15 (~6.3x) release-please release-please max 64s -> 10 (~9.4x) license-check check-license max 24s -> 5 (~12.5x) auto-assignment auto-assign max 24s -> 5 (~12.5x) csat welcome max 8s -> 5 (~37.5x) No strategy/fail-fast is added: none of these workflows has a matrix.
This was referenced Jul 31, 2026
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.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
Closes: #issue_number
Related: #issue_number
Problem: Every job in the five workflows below inherits GitHub's default job timeout of 360 minutes ("The maximum number of minutes to let a job run before GitHub automatically cancels it. Default: 360."). None of them sets
timeout-minutestoday. The one that matters iscross-language-integration.yml: its singlemacos-latestjob reaches the network three times without a cache (npm install, plusgo mod tidyintests/cross_language/a2a/go_ts/go_clientandtests/cross_language/a2a/ts_go/go_backend). If the npm registry or the Go module proxy wedges, the job sits there producing no output until the 6-hour cap fires — 10 hours of billed time, since macOS runners bill at 10x — and the PR check just spins. The other four are single-digit-secondbash/github-scriptjobs that have no legitimate reason to run for hours either.Solution: Add one job-level
timeout-minuteskey to each of the five workflows — five files, five inserted lines, zero deletions. Each value is sized from that workflow's own observed job execution time, not guessed and not copied between workflows.timeout-minutes.github/workflows/cross-language-integration.ymlrun-tests.github/workflows/release-please.ymlrelease-please.github/workflows/license-check.ymlcheck-license.github/workflows/auto-assignment.ymlauto-assign.github/workflows/csat.ymlwelcomeWhere the numbers come from
Measured against
google/adk-jsvia the public API on 2026-07-31: runs enumerated withgh run list --workflow <file> --status success, then per-jobstarted_at/completed_atread fromgh api repos/google/adk-js/actions/runs/<id>/jobs. These are job execution times. Run-level wall clock is not usable for this: it is inflated by runner queue wait, which is not charged againsttimeout-minutes(license-checkrun29957830146has a 758s run-level wall clock around a job that executed for 5s).cross-language-integration(run-tests, macos-latest, success)cross-language-integration(run-tests, failing runs)release-please(release-please)license-check(check-license)auto-assignment(auto-assign)csat(welcome, entire history)Per-workflow rationale:
cross-language-integration→ 15. Deliberately the loosest multiplier of the five, because it is the only job whose duration is genuinely variable:macos-latestis the slowest and most contended hosted pool, and the job makes three uncached network fetches. A cold registry, a slow proxy, or growth in the cross-language suite must not start producing spurious red checks. It still cuts the worst case from 360 minutes to 15 — a 24x reduction in wasted runner time, which is the whole point.release-please→ 10. Larger than the trivial three because on a release-cutting pushgoogleapis/release-please-actiondoes much more than on the common no-op push: it walks history and creates/updates release PRs, tags and releases for all four components inrelease-please-config.json(.,core,dev,integrations). That path is API-bound and under-represented in the p50.license-check/auto-assignment/csat→ 5 each. Onebashscript over the tree and twoactions/github-scriptcalls. A uniform 5 beats three bespoke values, which would encode false precision on a 6-second job.The 24s maxima on
license-check/auto-assignand the 143s max oncross-languageare recent-window outliers (the p95s are 8s and 127s); the chosen ceilings clear even those by 6x–12x.Placement follows
google/adk-python, which already uses this key at job level as a sibling ofruns-on:(.github/workflows/continuous-integration.ymlsetstimeout-minutes: 10;.github/workflows/issue-maintenance.ymlsets120and60).Deliberately not in this PR
strategy/fail-fast. All five of these workflows are single-job with no matrix (grep -rn strategy .github/workflows/matches onlyvalidation.yaml), andfail-fastis a matrix-only key — adding it here would be inert config..github/workflows/validation.yamlis untouched, and is confirmed absent from the diff. It is the subject of separate changes.concurrency, no action-version bumps or SHA pinning (csat.ymlstays oncheckout@v3/github-script@v6), no step-level timeouts. This is not a performance change.Collision check
Ran before writing anything, per contribution hygiene:
Exactly three branches add
timeout-minutesto a workflow, and all three touch only.github/workflows/validation.yaml(#218, #235, #276) — disjoint from these five files, so no stacking is needed and neither side can conflict. Four other open PRs editcross-language-integration.yml(#133, #306, #338, #393); none of them adds atimeoutkey of any kind (verified withgh pr diff <n> | grep -E '^[+-].*timeout'). No PR duplicates this change.Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.
No unit tests are added, deliberately: this change adds zero lines of executable code — five declarative YAML keys — so there is no surface for
vitestto cover, and the repo has no workflow-linting test to extend. Inventing a vitest suite that parses.github/workflows/*.ymlpurely to produce a coverage number would be the only test of its kind in the repo and would break on every legitimate workflow edit. The parse-and-assert protocol below is the substitute. Note also that nothing in the repo's own tooling validates these files:npm run lintiseslint "**/*.ts"andnpm run format:checkisprettier "**/*.ts" --check(neither matches.yml/.yaml), andscripts/check_license.shonly scans*.js/*.ts, which is why no license header was added to the edited YAML.1. Every workflow still parses (all six files, including the two untouched ones):
2. The key landed on the right job, with the right value and the right type:
The script asserts, per file, that the workflow declares exactly the expected single job id (renaming one would silently orphan any branch-protection rule matching the check by name), that
timeout-minutesequals the expected value, that it parsed as anintand not astr, thatruns-onsurvived, and that nostrategykey was introduced.Proof these assertions can actually fail. Both mutations were applied to the real files, run, and reverted:
timeout-minutes: "5"incsat.yml:AssertionError: .github/workflows/csat.yml: timeout-minutes is '5', expected 5check-licensejob and indented it under the first step inlicense-check.yml, which is the realistic version of this mistake. A naivegrep -c timeout-minuteson that file still returns1; the parsed-tree assertion catches it:AssertionError: .github/workflows/license-check.yml: timeout-minutes is None, expected 53. The diff is minimal:
Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.
The ceiling was observed firing, on a throwaway branch on a fork (never against
google/adk-js). To reproduce: settimeout-minutes: 1oncheck-license, insert a- run: sleep 300step before the license step, push, and open a PR so thepull_requesttrigger fires. Observed on that probe run (gh api repos/<fork>/actions/runs/<id>/jobs):The job was killed ~1 minute after start instead of running its 300-second sleep, and the check surfaced on the PR as a red, non-passing check in ~1m15s. One correction worth recording: the job's API
conclusioniscancelled, notfailure— it renders red and blocks a merge exactly like a failure, but anyone matching onconclusion == "failure"should know it iscancelled. The probe branch and its PR were deleted/closed;git diff main --shortstatis back to5 files changed, 5 insertions(+).Live checks on this PR exercise three of the five new ceilings for free, and all three passed well inside their new budgets — a healthy run is unaffected:
Cross-Language Tests / run-tests(15 min)License Header Check / check-license(5 min)auto-assignment / auto-assign(5 min)csat.ymlfires only onissues: closedandrelease-please.ymlonly onpushtomain, so neither can be exercised from a PR; their correctness rests on check 2 above, which validates the parsed job object rather than the file text.All checks on this PR are green, but one of them needed two reruns and that is worth disclosing rather than hiding:
validation / run-tests (windows-latest)— a job from the untouched.github/workflows/validation.yaml— failed twice (8m11s, 8m46s) before passing (9m13s), each failure being the same single test,tests/integration/app_loader/app_loader_test.ts > should discover apps vs agents across directories and standalone files, withError: Test timed out in 40000msagainst 2674 passing tests. That test measures coldAgentLoader.listApps()discovery against a 40s per-test cap and is the known slow-Windows-runner flake already being addressed by separate PRs; this change adds no executable code for any test to exercise. Corroboration that it is unrelated: the identical five workflow edits on the throwaway probe branch passedvalidationon all three OSes ten minutes before the first failure (run-tests (windows-latest)success, 16:24:39Z → 16:33:14Z), andrun-tests (ubuntu-latest)(5m28s) andrun-tests (macos-latest)(7m3s) were green on every attempt here.Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.