2025-12-04T08:11:24.9806474Z Current runner version: '2.329.0' 2025-12-04T08:11:24.9828857Z ##[group]Runner Image Provisioner 2025-12-04T08:11:24.9829600Z Hosted Compute Agent 2025-12-04T08:11:24.9830150Z Version: 20251124.448 2025-12-04T08:11:24.9830666Z Commit: fda5086b43ec66ade217e5fcd18146c879571177 2025-12-04T08:11:24.9831298Z Build Date: 2025-11-24T21:16:26Z 2025-12-04T08:11:24.9831820Z ##[endgroup] 2025-12-04T08:11:24.9832334Z ##[group]Operating System 2025-12-04T08:11:24.9832812Z Ubuntu 2025-12-04T08:11:24.9833240Z 24.04.3 2025-12-04T08:11:24.9833636Z LTS 2025-12-04T08:11:24.9834033Z ##[endgroup] 2025-12-04T08:11:24.9834613Z ##[group]Runner Image 2025-12-04T08:11:24.9835082Z Image: ubuntu-24.04 2025-12-04T08:11:24.9835535Z Version: 20251126.144.1 2025-12-04T08:11:24.9836394Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251126.144/images/ubuntu/Ubuntu2404-Readme.md 2025-12-04T08:11:24.9837812Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251126.144 2025-12-04T08:11:24.9838642Z ##[endgroup] 2025-12-04T08:11:24.9839538Z ##[group]GITHUB_TOKEN Permissions 2025-12-04T08:11:24.9841508Z Contents: read 2025-12-04T08:11:24.9841997Z Metadata: read 2025-12-04T08:11:24.9842441Z ##[endgroup] 2025-12-04T08:11:24.9844471Z Secret source: Actions 2025-12-04T08:11:24.9845154Z Prepare workflow directory 2025-12-04T08:11:25.0289226Z Prepare all required actions 2025-12-04T08:11:25.0343223Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (ffd9b0fb4355e97af82fc42cf185c3ffa0fc0a32) 2025-12-04T08:11:25.0347669Z ##[group] Inputs 2025-12-04T08:11:25.0348199Z check_experiments: 2025-12-04T08:11:25.0348708Z opt_out_experiments: lf 2025-12-04T08:11:25.0349237Z triggering_actor: pytorchmergebot 2025-12-04T08:11:25.0349799Z issue_owner: 2025-12-04T08:11:25.0350221Z curr_branch: main 2025-12-04T08:11:25.0350693Z curr_ref_type: branch 2025-12-04T08:11:25.0351221Z issue_number: 5132 2025-12-04T08:11:25.0351692Z ##[endgroup] 2025-12-04T08:11:25.0352279Z Complete job name: get-label-type / runner-determinator 2025-12-04T08:11:25.7532325Z ##[group]Run cat < runner_determinator.py 2025-12-04T08:11:25.7534922Z cat < runner_determinator.py 2025-12-04T08:11:25.7535585Z # flake8: noqa: G004 2025-12-04T08:11:25.7536181Z  2025-12-04T08:11:25.7536938Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T08:11:25.7538023Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T08:11:25.7539007Z # python .github/scripts/update_runner_determinator.py 2025-12-04T08:11:25.7539697Z  2025-12-04T08:11:25.7540155Z """ 2025-12-04T08:11:25.7540857Z This runner determinator is used to determine which set of runners to run a 2025-12-04T08:11:25.7541891Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T08:11:25.7543027Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T08:11:25.7544043Z of which runners should be used to run which job. 2025-12-04T08:11:25.7544964Z  2025-12-04T08:11:25.7545679Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T08:11:25.7546780Z separated by a line containing "---". If the line is not present, the 2025-12-04T08:11:25.7547774Z settings are considered to be empty with only the second part, the user 2025-12-04T08:11:25.7548604Z list, defined. 2025-12-04T08:11:25.7549127Z  2025-12-04T08:11:25.7549815Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T08:11:25.7550874Z used to define any settings that are needed to determine which runners to use. 2025-12-04T08:11:25.7551887Z It's fields are defined by the RolloutSettings class below. 2025-12-04T08:11:25.7552620Z  2025-12-04T08:11:25.7553516Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T08:11:25.7554662Z The user list is also a comma separated list of additional features or 2025-12-04T08:11:25.7555559Z experiments which the user could be opted in to. 2025-12-04T08:11:25.7556213Z  2025-12-04T08:11:25.7556752Z The user list has the following rules: 2025-12-04T08:11:25.7557351Z  2025-12-04T08:11:25.7558040Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T08:11:25.7559025Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T08:11:25.7559956Z - A "#" prefix opts the user out of all experiments 2025-12-04T08:11:25.7560626Z  2025-12-04T08:11:25.7561080Z Example config: 2025-12-04T08:11:25.7561717Z  # A list of experiments that can be opted into. 2025-12-04T08:11:25.7562536Z  # This defines the behavior they'll induce when opted into. 2025-12-04T08:11:25.7563306Z  # Expected syntax is: 2025-12-04T08:11:25.7564129Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T08:11:25.7565289Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T08:11:25.7566178Z  2025-12-04T08:11:25.7566629Z  experiments: 2025-12-04T08:11:25.7567178Z  lf: 2025-12-04T08:11:25.7567651Z  rollout_percent: 25 2025-12-04T08:11:25.7568288Z  all_branches: false 2025-12-04T08:11:25.7568892Z  default: true 2025-12-04T08:11:25.7569403Z  --- 2025-12-04T08:11:25.7569915Z  2025-12-04T08:11:25.7570368Z  # Opt-ins: 2025-12-04T08:11:25.7571106Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T08:11:25.7572238Z  # and specifying experiments to enable in a comma-separated list. 2025-12-04T08:11:25.7573179Z  # To always opt out of an experiment, prefix it with a "-". 2025-12-04T08:11:25.7573984Z  # Experiments should be from the above list. 2025-12-04T08:11:25.7574775Z  2025-12-04T08:11:25.7575269Z  @User1,-lf,split_build 2025-12-04T08:11:25.7575831Z  @User2,lf 2025-12-04T08:11:25.7576681Z  @User3,split_build 2025-12-04T08:11:25.7577224Z """ 2025-12-04T08:11:25.7577699Z  2025-12-04T08:11:25.7578180Z import json 2025-12-04T08:11:25.7578671Z import logging 2025-12-04T08:11:25.7579215Z import os 2025-12-04T08:11:25.7579708Z import random 2025-12-04T08:11:25.7580246Z import re 2025-12-04T08:11:25.7580717Z import sys 2025-12-04T08:11:25.7581284Z from argparse import ArgumentParser 2025-12-04T08:11:25.7582051Z from collections.abc import Iterable 2025-12-04T08:11:25.7582720Z from functools import cache 2025-12-04T08:11:25.7583360Z from logging import LogRecord 2025-12-04T08:11:25.7583999Z from typing import Any, NamedTuple 2025-12-04T08:11:25.7584934Z from urllib.request import Request, urlopen 2025-12-04T08:11:25.7585768Z  2025-12-04T08:11:25.7586212Z import yaml 2025-12-04T08:11:25.7586775Z from github import Auth, Github 2025-12-04T08:11:25.7587363Z from github.Issue import Issue 2025-12-04T08:11:25.7587996Z  2025-12-04T08:11:25.7588406Z  2025-12-04T08:11:25.7589018Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T08:11:25.7589893Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T08:11:25.7590902Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T08:11:25.7591714Z  2025-12-04T08:11:25.7592443Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T08:11:25.7593155Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T08:11:25.7593792Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T08:11:25.7594672Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T08:11:25.7595332Z  2025-12-04T08:11:25.7595811Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T08:11:25.7596446Z  2025-12-04T08:11:25.7596897Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T08:11:25.7597512Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T08:11:25.7598066Z  2025-12-04T08:11:25.7598549Z  2025-12-04T08:11:25.7599041Z class Experiment(NamedTuple): 2025-12-04T08:11:25.7599848Z  rollout_perc: float = ( 2025-12-04T08:11:25.7600671Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T08:11:25.7601440Z  ) 2025-12-04T08:11:25.7601948Z  all_branches: bool = ( 2025-12-04T08:11:25.7602760Z  False # If True, the experiment is also enabled on the exception branches 2025-12-04T08:11:25.7603561Z  ) 2025-12-04T08:11:25.7604057Z  default: bool = ( 2025-12-04T08:11:25.7604842Z  True # If True, the experiment is enabled by default for all queries 2025-12-04T08:11:25.7605613Z  ) 2025-12-04T08:11:25.7606038Z  2025-12-04T08:11:25.7606575Z  # Add more fields as needed 2025-12-04T08:11:25.7607135Z  2025-12-04T08:11:25.7607569Z  2025-12-04T08:11:25.7608093Z class Settings(NamedTuple): 2025-12-04T08:11:25.7608659Z  """ 2025-12-04T08:11:25.7609257Z  Settings for the experiments that can be opted into. 2025-12-04T08:11:25.7609976Z  """ 2025-12-04T08:11:25.7610454Z  2025-12-04T08:11:25.7610945Z  experiments: dict[str, Experiment] = {} 2025-12-04T08:11:25.7611609Z  2025-12-04T08:11:25.7612123Z  2025-12-04T08:11:25.7612658Z class ColorFormatter(logging.Formatter): 2025-12-04T08:11:25.7613516Z  """Color codes the log messages based on the log level""" 2025-12-04T08:11:25.7614194Z  2025-12-04T08:11:25.7614725Z  COLORS = { 2025-12-04T08:11:25.7615269Z  "WARNING": "\033[33m", # Yellow 2025-12-04T08:11:25.7615926Z  "ERROR": "\033[31m", # Red 2025-12-04T08:11:25.7616541Z  "CRITICAL": "\033[31m", # Red 2025-12-04T08:11:25.7617214Z  "INFO": "\033[0m", # Reset 2025-12-04T08:11:25.7617867Z  "DEBUG": "\033[0m", # Reset 2025-12-04T08:11:25.7618440Z  } 2025-12-04T08:11:25.7618924Z  2025-12-04T08:11:25.7619439Z  def format(self, record: LogRecord) -> str: 2025-12-04T08:11:25.7620330Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T08:11:25.7621288Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T08:11:25.7621985Z  return super().format(record) 2025-12-04T08:11:25.7622595Z  2025-12-04T08:11:25.7623005Z  2025-12-04T08:11:25.7623552Z handler = logging.StreamHandler() 2025-12-04T08:11:25.7624605Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T08:11:25.7625461Z  2025-12-04T08:11:25.7626081Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T08:11:25.7626770Z log.addHandler(handler) 2025-12-04T08:11:25.7627357Z log.setLevel(logging.INFO) 2025-12-04T08:11:25.7627939Z  2025-12-04T08:11:25.7628401Z  2025-12-04T08:11:25.7628966Z def set_github_output(key: str, value: str) -> None: 2025-12-04T08:11:25.7629678Z  """ 2025-12-04T08:11:25.7630297Z  Defines outputs of the github action that invokes this script 2025-12-04T08:11:25.7631161Z  """ 2025-12-04T08:11:25.7631699Z  if not GITHUB_OUTPUT: 2025-12-04T08:11:25.7632903Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T08:11:25.7634147Z  log.warning( 2025-12-04T08:11:25.7635208Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T08:11:25.7636253Z  ) 2025-12-04T08:11:25.7636834Z  print(f"::set-output name={key}::{value}") 2025-12-04T08:11:25.7637476Z  return 2025-12-04T08:11:25.7638018Z  2025-12-04T08:11:25.7638498Z  with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T08:11:25.7639225Z  log.info(f"Setting output: {key}='{value}'") 2025-12-04T08:11:25.7639945Z  f.write(f"{key}={value}\n") 2025-12-04T08:11:25.7640522Z  2025-12-04T08:11:25.7640992Z  2025-12-04T08:11:25.7641613Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T08:11:25.7642387Z  return frozenset( 2025-12-04T08:11:25.7643113Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T08:11:25.7643928Z  ) 2025-12-04T08:11:25.7644466Z  2025-12-04T08:11:25.7644867Z  2025-12-04T08:11:25.7645412Z def parse_args() -> Any: 2025-12-04T08:11:25.7646100Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T08:11:25.7647084Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T08:11:25.7647960Z  parser.add_argument( 2025-12-04T08:11:25.7648567Z  "--github-issue-repo", 2025-12-04T08:11:25.7649160Z  type=str, 2025-12-04T08:11:25.7649741Z  required=False, 2025-12-04T08:11:25.7650432Z  default="pytorch/test-infra", 2025-12-04T08:11:25.7651087Z  help="GitHub repo to get the issue", 2025-12-04T08:11:25.7651739Z  ) 2025-12-04T08:11:25.7652208Z  parser.add_argument( 2025-12-04T08:11:25.7652801Z  "--github-repo", 2025-12-04T08:11:25.7653423Z  type=str, 2025-12-04T08:11:25.7653948Z  required=True, 2025-12-04T08:11:25.7654637Z  help="GitHub repo where CI is running", 2025-12-04T08:11:25.7655256Z  ) 2025-12-04T08:11:25.7655770Z  parser.add_argument( 2025-12-04T08:11:25.7656508Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T08:11:25.7657324Z  ) 2025-12-04T08:11:25.7657834Z  parser.add_argument( 2025-12-04T08:11:25.7658590Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T08:11:25.7659394Z  ) 2025-12-04T08:11:25.7659870Z  parser.add_argument( 2025-12-04T08:11:25.7660677Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T08:11:25.7661453Z  ) 2025-12-04T08:11:25.7661976Z  parser.add_argument( 2025-12-04T08:11:25.7662798Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T08:11:25.7663579Z  ) 2025-12-04T08:11:25.7664116Z  parser.add_argument( 2025-12-04T08:11:25.7664963Z  "--github-ref-type", 2025-12-04T08:11:25.7665578Z  type=str, 2025-12-04T08:11:25.7666135Z  required=True, 2025-12-04T08:11:25.7666833Z  help="Current GitHub ref type, branch or tag", 2025-12-04T08:11:25.7667458Z  ) 2025-12-04T08:11:25.7667993Z  parser.add_argument( 2025-12-04T08:11:25.7668670Z  "--eligible-experiments", 2025-12-04T08:11:25.7669351Z  type=_str_comma_separated_to_set, 2025-12-04T08:11:25.7670002Z  required=False, 2025-12-04T08:11:25.7670583Z  default="", 2025-12-04T08:11:25.7671575Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T08:11:25.7672609Z  ) 2025-12-04T08:11:25.7673131Z  parser.add_argument( 2025-12-04T08:11:25.7673711Z  "--opt-out-experiments", 2025-12-04T08:11:25.7674573Z  type=_str_comma_separated_to_set, 2025-12-04T08:11:25.7675193Z  required=False, 2025-12-04T08:11:25.7675772Z  default="", 2025-12-04T08:11:25.7676338Z  help=( 2025-12-04T08:11:25.7677139Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T08:11:25.7678422Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T08:11:25.7679346Z  ), 2025-12-04T08:11:25.7679845Z  ) 2025-12-04T08:11:25.7680349Z  parser.add_argument( 2025-12-04T08:11:25.7680914Z  "--pr-number", 2025-12-04T08:11:25.7681513Z  type=str, 2025-12-04T08:11:25.7682039Z  required=False, 2025-12-04T08:11:25.7682613Z  default="", 2025-12-04T08:11:25.7683235Z  help="the optional PR number where this is run", 2025-12-04T08:11:25.7683914Z  ) 2025-12-04T08:11:25.7684447Z  2025-12-04T08:11:25.7684924Z  return parser.parse_args() 2025-12-04T08:11:25.7685545Z  2025-12-04T08:11:25.7685948Z  2025-12-04T08:11:25.7686700Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T08:11:25.7687647Z  auth = Auth.Token(github_token) 2025-12-04T08:11:25.7688298Z  return Github(auth=auth) 2025-12-04T08:11:25.7688937Z  2025-12-04T08:11:25.7689357Z  2025-12-04T08:11:25.7690115Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T08:11:25.7691033Z  repo = gh.get_repo(repo) 2025-12-04T08:11:25.7691684Z  return repo.get_issue(number=issue_num) 2025-12-04T08:11:25.7692293Z  2025-12-04T08:11:25.7692785Z  2025-12-04T08:11:25.7693230Z def get_potential_pr_author( 2025-12-04T08:11:25.7694030Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T08:11:25.7695245Z ) -> str: 2025-12-04T08:11:25.7695885Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T08:11:25.7696838Z  # Fetch the actual username from the original PR. The PR number is 2025-12-04T08:11:25.7697710Z  # embedded in the tag name: ciflow// 2025-12-04T08:11:25.7698430Z  2025-12-04T08:11:25.7698932Z  gh = get_gh_client(github_token) 2025-12-04T08:11:25.7699517Z  2025-12-04T08:11:25.7700112Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T08:11:25.7700843Z  split_tag = ref_name.split("/") 2025-12-04T08:11:25.7701495Z  if ( 2025-12-04T08:11:25.7702007Z  len(split_tag) == 3 2025-12-04T08:11:25.7702644Z  and split_tag[0] == "ciflow" 2025-12-04T08:11:25.7703319Z  and split_tag[2].isnumeric() 2025-12-04T08:11:25.7703916Z  ): 2025-12-04T08:11:25.7704651Z  pr_number = split_tag[2] 2025-12-04T08:11:25.7705230Z  try: 2025-12-04T08:11:25.7705889Z  repository = gh.get_repo(repo) 2025-12-04T08:11:25.7706759Z  pull = repository.get_pull(number=int(pr_number)) 2025-12-04T08:11:25.7707584Z  except Exception as e: 2025-12-04T08:11:25.7708290Z  raise Exception( # noqa: TRY002 2025-12-04T08:11:25.7709073Z  f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T08:11:25.7709853Z  ) from e 2025-12-04T08:11:25.7710561Z  return pull.user.login # type: ignore[no-any-return] 2025-12-04T08:11:25.7711403Z  # In all other cases, return the original input username 2025-12-04T08:11:25.7712107Z  return username 2025-12-04T08:11:25.7712649Z  2025-12-04T08:11:25.7713119Z  2025-12-04T08:11:25.7713646Z def is_exception_branch(branch: str) -> bool: 2025-12-04T08:11:25.7714383Z  """ 2025-12-04T08:11:25.7715152Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T08:11:25.7716055Z  """ 2025-12-04T08:11:25.7716771Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T08:11:25.7717517Z  2025-12-04T08:11:25.7717988Z  2025-12-04T08:11:25.7718465Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T08:11:25.7719090Z  try: 2025-12-04T08:11:25.7719587Z  data = yaml.safe_load(yaml_text) 2025-12-04T08:11:25.7720229Z  return data 2025-12-04T08:11:25.7720807Z  except yaml.YAMLError: 2025-12-04T08:11:25.7721435Z  log.exception("Error loading YAML") 2025-12-04T08:11:25.7722073Z  raise 2025-12-04T08:11:25.7722561Z  2025-12-04T08:11:25.7723011Z  2025-12-04T08:11:25.7723693Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T08:11:25.7724639Z  """ 2025-12-04T08:11:25.7725507Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T08:11:25.7726327Z  2025-12-04T08:11:25.7727025Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T08:11:25.7727897Z  and the text below is the list of opted in users. 2025-12-04T08:11:25.7728549Z  2025-12-04T08:11:25.7729711Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T08:11:25.7730486Z  """ 2025-12-04T08:11:25.7731051Z  rollout_state_parts = rollout_state.split("---") 2025-12-04T08:11:25.7731777Z  if len(rollout_state_parts) >= 2: 2025-12-04T08:11:25.7732521Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T08:11:25.7733202Z  else: 2025-12-04T08:11:25.7733762Z  return "", rollout_state 2025-12-04T08:11:25.7734412Z  2025-12-04T08:11:25.7734833Z  2025-12-04T08:11:25.7735363Z class UserOptins(dict[str, list[str]]): 2025-12-04T08:11:25.7735958Z  """ 2025-12-04T08:11:25.7736610Z  Dictionary of users with a list of features they have opted into 2025-12-04T08:11:25.7737337Z  """ 2025-12-04T08:11:25.7737843Z  2025-12-04T08:11:25.7738279Z  2025-12-04T08:11:25.7738908Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T08:11:25.7739685Z  """ 2025-12-04T08:11:25.7740499Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-12-04T08:11:25.7741437Z  2025-12-04T08:11:25.7742357Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T08:11:25.7743461Z  - Example line: "@User1,lf,split_build" 2025-12-04T08:11:25.7744644Z  - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T08:11:25.7745400Z  2025-12-04T08:11:25.7745845Z  2025-12-04T08:11:25.7746262Z  """ 2025-12-04T08:11:25.7746796Z  optins = UserOptins() 2025-12-04T08:11:25.7747434Z  for user in user_optin_text.split("\n"): 2025-12-04T08:11:25.7748081Z  user = user.strip("\r\n\t -") 2025-12-04T08:11:25.7748800Z  if not user or not user.startswith("@"): 2025-12-04T08:11:25.7749445Z  # Not a valid user. Skip 2025-12-04T08:11:25.7750063Z  continue 2025-12-04T08:11:25.7750590Z  2025-12-04T08:11:25.7751037Z  if user: 2025-12-04T08:11:25.7751614Z  usr_name = user.split(",")[0].strip("@") 2025-12-04T08:11:25.7752437Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T08:11:25.7753193Z  2025-12-04T08:11:25.7753643Z  return optins 2025-12-04T08:11:25.7754194Z  2025-12-04T08:11:25.7754989Z  2025-12-04T08:11:25.7755615Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T08:11:25.7756383Z  """ 2025-12-04T08:11:25.7756895Z  Check if the experiment name is valid. 2025-12-04T08:11:25.7757567Z  A valid name: 2025-12-04T08:11:25.7758334Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T08:11:25.7759396Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T08:11:25.7760195Z  - Cannot contain spaces 2025-12-04T08:11:25.7760787Z  """ 2025-12-04T08:11:25.7761258Z  2025-12-04T08:11:25.7761817Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T08:11:25.7762666Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T08:11:25.7763457Z  2025-12-04T08:11:25.7763909Z  if valid: 2025-12-04T08:11:25.7764455Z  return True 2025-12-04T08:11:25.7765025Z  2025-12-04T08:11:25.7765500Z  log.error( 2025-12-04T08:11:25.7767009Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-12-04T08:11:25.7768661Z  ) 2025-12-04T08:11:25.7769114Z  return False 2025-12-04T08:11:25.7769617Z  2025-12-04T08:11:25.7770080Z  2025-12-04T08:11:25.7770693Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T08:11:25.7771416Z  """ 2025-12-04T08:11:25.7772129Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T08:11:25.7772961Z  """ 2025-12-04T08:11:25.7773404Z  try: 2025-12-04T08:11:25.7773912Z  if settings_text: 2025-12-04T08:11:25.7774863Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T08:11:25.7775738Z  # for easy reading 2025-12-04T08:11:25.7776707Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T08:11:25.7777682Z  # the backtick character in shell commands. 2025-12-04T08:11:25.7778416Z  backtick = chr(96) # backtick character 2025-12-04T08:11:25.7779237Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T08:11:25.7780016Z  settings = load_yaml(settings_text) 2025-12-04T08:11:25.7780644Z  2025-12-04T08:11:25.7781332Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T08:11:25.7782267Z  experiments = {} 2025-12-04T08:11:25.7782840Z  2025-12-04T08:11:25.7783484Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T08:11:25.7784652Z  if not is_valid_experiment_name(exp_name): 2025-12-04T08:11:25.7785860Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-12-04T08:11:25.7787013Z  continue 2025-12-04T08:11:25.7787589Z  2025-12-04T08:11:25.7788053Z  valid_settings = {} 2025-12-04T08:11:25.7788749Z  for setting in exp_settings: 2025-12-04T08:11:25.7789420Z  if setting not in Experiment._fields: 2025-12-04T08:11:25.7790100Z  log.warning( 2025-12-04T08:11:25.7790958Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T08:11:25.7791758Z  ) 2025-12-04T08:11:25.7792326Z  else: 2025-12-04T08:11:25.7792947Z  valid_settings[setting] = exp_settings[setting] 2025-12-04T08:11:25.7793671Z  2025-12-04T08:11:25.7794223Z  experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T08:11:25.7795031Z  return Settings(experiments) 2025-12-04T08:11:25.7795674Z  2025-12-04T08:11:25.7796108Z  except Exception: 2025-12-04T08:11:25.7796744Z  log.exception("Failed to parse settings") 2025-12-04T08:11:25.7797387Z  2025-12-04T08:11:25.7797856Z  return Settings() 2025-12-04T08:11:25.7798358Z  2025-12-04T08:11:25.7798806Z  2025-12-04T08:11:25.7799430Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T08:11:25.7800143Z  """ 2025-12-04T08:11:25.7800922Z  Parse settings, if any, from the rollout state. 2025-12-04T08:11:25.7801550Z  2025-12-04T08:11:25.7802204Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T08:11:25.7803065Z  and the text below is the list of opted in users. 2025-12-04T08:11:25.7803740Z  2025-12-04T08:11:25.7804671Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T08:11:25.7805496Z  """ 2025-12-04T08:11:25.7806200Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:11:25.7807060Z  return parse_settings_from_text(settings_text) 2025-12-04T08:11:25.7807728Z  2025-12-04T08:11:25.7808140Z  2025-12-04T08:11:25.7808739Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T08:11:25.7809428Z  """ 2025-12-04T08:11:25.7809937Z  Parse users from the rollout state. 2025-12-04T08:11:25.7810552Z  2025-12-04T08:11:25.7810947Z  """ 2025-12-04T08:11:25.7811649Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:11:25.7812509Z  return parse_user_opt_in_from_text(users_text) 2025-12-04T08:11:25.7813175Z  2025-12-04T08:11:25.7813639Z  2025-12-04T08:11:25.7814402Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:11:25.7815252Z  """ 2025-12-04T08:11:25.7815815Z  Check if a user is opted into an experiment 2025-12-04T08:11:25.7816481Z  """ 2025-12-04T08:11:25.7817045Z  return experiment_name in user_optins.get(user, []) 2025-12-04T08:11:25.7817758Z  2025-12-04T08:11:25.7818302Z  2025-12-04T08:11:25.7819010Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:11:25.7819890Z  """ 2025-12-04T08:11:25.7820478Z  Check if a user explicitly opted out of an experiment 2025-12-04T08:11:25.7821173Z  """ 2025-12-04T08:11:25.7821790Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T08:11:25.7822621Z  experiment_optout = "-" + experiment_name 2025-12-04T08:11:25.7823399Z  if experiment_optout not in user_optins.get(user, []): 2025-12-04T08:11:25.7824094Z  return False 2025-12-04T08:11:25.7824844Z  2025-12-04T08:11:25.7825394Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T08:11:25.7826119Z  log.warning( 2025-12-04T08:11:25.7827092Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T08:11:25.7828039Z  ) 2025-12-04T08:11:25.7828526Z  2025-12-04T08:11:25.7828977Z  return True 2025-12-04T08:11:25.7829482Z  2025-12-04T08:11:25.7829876Z  2025-12-04T08:11:25.7830368Z def get_runner_prefix( 2025-12-04T08:11:25.7830912Z  rollout_state: str, 2025-12-04T08:11:25.7831508Z  workflow_requestors: Iterable[str], 2025-12-04T08:11:25.7832187Z  branch: str, 2025-12-04T08:11:25.7832796Z  eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T08:11:25.7833580Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T08:11:25.7834656Z  is_canary: bool = False, 2025-12-04T08:11:25.7835246Z ) -> str: 2025-12-04T08:11:25.7835794Z  settings = parse_settings(rollout_state) 2025-12-04T08:11:25.7836525Z  user_optins = parse_users(rollout_state) 2025-12-04T08:11:25.7837158Z  2025-12-04T08:11:25.7837691Z  fleet_prefix = "" 2025-12-04T08:11:25.7838281Z  prefixes = [] 2025-12-04T08:11:25.7839018Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T08:11:25.7840101Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T08:11:25.7840948Z  log.info( 2025-12-04T08:11:25.7841732Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T08:11:25.7842589Z  ) 2025-12-04T08:11:25.7843127Z  continue 2025-12-04T08:11:25.7843650Z  2025-12-04T08:11:25.7844116Z  if opt_out_experiments: 2025-12-04T08:11:25.7936349Z  if experiment_name in opt_out_experiments: 2025-12-04T08:11:25.7937725Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T08:11:25.7938997Z  log.info( 2025-12-04T08:11:25.7940713Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T08:11:25.7941746Z  ) 2025-12-04T08:11:25.7942206Z  continue 2025-12-04T08:11:25.7942689Z  2025-12-04T08:11:25.7943091Z  if eligible_experiments: 2025-12-04T08:11:25.7943727Z  if experiment_name not in eligible_experiments: 2025-12-04T08:11:25.7944635Z  exp_list = ", ".join(eligible_experiments) 2025-12-04T08:11:25.7945229Z  log.info( 2025-12-04T08:11:25.7946076Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T08:11:25.7946938Z  ) 2025-12-04T08:11:25.7947402Z  continue 2025-12-04T08:11:25.7948175Z  elif not experiment_settings.default: 2025-12-04T08:11:25.7948747Z  log.info( 2025-12-04T08:11:25.7949475Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T08:11:25.7950237Z  ) 2025-12-04T08:11:25.7950680Z  continue 2025-12-04T08:11:25.7951139Z  2025-12-04T08:11:25.7951651Z  # Is any workflow_requestor opted out to this experiment? 2025-12-04T08:11:25.7952319Z  opted_out_users = [ 2025-12-04T08:11:25.7952843Z  requestor 2025-12-04T08:11:25.7953383Z  for requestor in workflow_requestors 2025-12-04T08:11:25.7954111Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T08:11:25.7955002Z  ] 2025-12-04T08:11:25.7955409Z  2025-12-04T08:11:25.7955820Z  if opted_out_users: 2025-12-04T08:11:25.7956368Z  log.info( 2025-12-04T08:11:25.7957466Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T08:11:25.7958347Z  ) 2025-12-04T08:11:25.7958778Z  continue 2025-12-04T08:11:25.7959226Z  2025-12-04T08:11:25.7959719Z  # Is any workflow_requestor opted in to this experiment? 2025-12-04T08:11:25.7960382Z  opted_in_users = [ 2025-12-04T08:11:25.7960882Z  requestor 2025-12-04T08:11:25.7961407Z  for requestor in workflow_requestors 2025-12-04T08:11:25.7962118Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T08:11:25.7962760Z  ] 2025-12-04T08:11:25.7963161Z  2025-12-04T08:11:25.7963541Z  enabled = False 2025-12-04T08:11:25.7964043Z  if opted_in_users: 2025-12-04T08:11:25.7965143Z  log.info( 2025-12-04T08:11:25.7965828Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T08:11:25.7966533Z  ) 2025-12-04T08:11:25.7966975Z  enabled = True 2025-12-04T08:11:25.7967458Z  2025-12-04T08:11:25.7967897Z  elif experiment_settings.rollout_perc: 2025-12-04T08:11:25.7968775Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T08:11:25.7969767Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T08:11:25.7970447Z  log.info( 2025-12-04T08:11:25.7971367Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T08:11:25.7972304Z  ) 2025-12-04T08:11:25.7972777Z  enabled = True 2025-12-04T08:11:25.7973272Z  2025-12-04T08:11:25.7973640Z  if enabled: 2025-12-04T08:11:25.7974133Z  label = experiment_name 2025-12-04T08:11:25.7974859Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T08:11:25.7975723Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T08:11:25.7976623Z  # - If it's enabled, then we always list it's prefix first 2025-12-04T08:11:25.7977430Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T08:11:25.7978134Z  if is_canary: 2025-12-04T08:11:25.7978686Z  label += CANARY_FLEET_SUFFIX 2025-12-04T08:11:25.7979265Z  fleet_prefix = label 2025-12-04T08:11:25.7979786Z  else: 2025-12-04T08:11:25.7980359Z  prefixes.append(label) 2025-12-04T08:11:25.7980882Z  2025-12-04T08:11:25.7981271Z  if len(prefixes) > 1: 2025-12-04T08:11:25.7981770Z  log.error( 2025-12-04T08:11:25.7982847Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-12-04T08:11:25.7983976Z  ) 2025-12-04T08:11:25.7984633Z  prefixes = prefixes[:1] 2025-12-04T08:11:25.7985150Z  2025-12-04T08:11:25.7985542Z  # Fleet always comes first 2025-12-04T08:11:25.7986070Z  if fleet_prefix: 2025-12-04T08:11:25.7986604Z  prefixes.insert(0, fleet_prefix) 2025-12-04T08:11:25.7987146Z  2025-12-04T08:11:25.7987625Z  return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T08:11:25.7988214Z  2025-12-04T08:11:25.7988579Z  2025-12-04T08:11:25.7989241Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T08:11:25.7990030Z  """ 2025-12-04T08:11:25.7990663Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T08:11:25.7991389Z  2025-12-04T08:11:25.7992000Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T08:11:25.7992727Z  """ 2025-12-04T08:11:25.7993168Z  gh = get_gh_client(github_token) 2025-12-04T08:11:25.7993753Z  issue = get_issue(gh, repo, issue_num) 2025-12-04T08:11:25.7994572Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T08:11:25.7995193Z  2025-12-04T08:11:25.7995552Z  2025-12-04T08:11:25.7996178Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T08:11:25.7997055Z  for _ in range(num_retries): 2025-12-04T08:11:25.7997583Z  try: 2025-12-04T08:11:25.7998070Z  req = Request(url=url, headers=headers) 2025-12-04T08:11:25.7998783Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T08:11:25.7999468Z  return json.loads(content) 2025-12-04T08:11:25.8000034Z  except Exception as e: 2025-12-04T08:11:25.8000646Z  log.warning(f"Could not download {url}: {e}") 2025-12-04T08:11:25.8001218Z  2025-12-04T08:11:25.8001825Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T08:11:25.8002563Z  return {} 2025-12-04T08:11:25.8002987Z  2025-12-04T08:11:25.8003349Z  2025-12-04T08:11:25.8003729Z @cache 2025-12-04T08:11:25.8004869Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T08:11:25.8005689Z  """ 2025-12-04T08:11:25.8006133Z  Dynamically get PR information 2025-12-04T08:11:25.8006655Z  """ 2025-12-04T08:11:25.8007210Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T08:11:25.8007876Z  headers = { 2025-12-04T08:11:25.8008409Z  "Accept": "application/vnd.github.v3+json", 2025-12-04T08:11:25.8009060Z  "Authorization": f"token {github_token}", 2025-12-04T08:11:25.8009611Z  } 2025-12-04T08:11:25.8010091Z  json_response: dict[str, Any] = download_json( 2025-12-04T08:11:25.8010742Z  url=f"{github_api}/issues/{pr_number}", 2025-12-04T08:11:25.8011321Z  headers=headers, 2025-12-04T08:11:25.8011797Z  ) 2025-12-04T08:11:25.8012186Z  2025-12-04T08:11:25.8012599Z  if not json_response: 2025-12-04T08:11:25.8013249Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T08:11:25.8014028Z  return {} 2025-12-04T08:11:25.8014628Z  2025-12-04T08:11:25.8015012Z  return json_response 2025-12-04T08:11:25.8015486Z  2025-12-04T08:11:25.8015844Z  2025-12-04T08:11:25.8016456Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T08:11:25.8017204Z  """ 2025-12-04T08:11:25.8017773Z  Dynamically get the latest list of labels from the pull request 2025-12-04T08:11:25.8018442Z  """ 2025-12-04T08:11:25.8018976Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T08:11:25.8019613Z  return { 2025-12-04T08:11:25.8020256Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T08:11:25.8020966Z  } 2025-12-04T08:11:25.8021344Z  2025-12-04T08:11:25.8021705Z  2025-12-04T08:11:25.8022090Z def main() -> None: 2025-12-04T08:11:25.8022561Z  args = parse_args() 2025-12-04T08:11:25.8023033Z  2025-12-04T08:11:25.8023480Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T08:11:25.8024035Z  2025-12-04T08:11:25.8024550Z  # Check if the PR is opt-out 2025-12-04T08:11:25.8025083Z  if args.pr_number: 2025-12-04T08:11:25.8025800Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T08:11:25.8026577Z  if OPT_OUT_LABEL in labels: 2025-12-04T08:11:25.8027111Z  log.info( 2025-12-04T08:11:25.8027857Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T08:11:25.8028653Z  ) 2025-12-04T08:11:25.8029271Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:11:25.8029974Z  sys.exit() 2025-12-04T08:11:25.8030511Z  2025-12-04T08:11:25.8030875Z  try: 2025-12-04T08:11:25.8031358Z  rollout_state = get_rollout_state_from_issue( 2025-12-04T08:11:25.8032116Z  args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T08:11:25.8032778Z  ) 2025-12-04T08:11:25.8033180Z  2025-12-04T08:11:25.8033602Z  username = get_potential_pr_author( 2025-12-04T08:11:25.8034181Z  args.github_token, 2025-12-04T08:11:25.8034962Z  args.github_repo, 2025-12-04T08:11:25.8035494Z  args.github_actor, 2025-12-04T08:11:25.8036035Z  args.github_ref_type, 2025-12-04T08:11:25.8036587Z  args.github_branch, 2025-12-04T08:11:25.8037096Z  ) 2025-12-04T08:11:25.8037492Z  2025-12-04T08:11:25.8038009Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T08:11:25.8038640Z  2025-12-04T08:11:25.8039090Z  runner_label_prefix = get_runner_prefix( 2025-12-04T08:11:25.8039670Z  rollout_state, 2025-12-04T08:11:25.8040224Z  (args.github_issue_owner, username), 2025-12-04T08:11:25.8040810Z  args.github_branch, 2025-12-04T08:11:25.8041360Z  args.eligible_experiments, 2025-12-04T08:11:25.8041944Z  args.opt_out_experiments, 2025-12-04T08:11:25.8042478Z  is_canary, 2025-12-04T08:11:25.8042944Z  ) 2025-12-04T08:11:25.8043334Z  2025-12-04T08:11:25.8043720Z  except Exception as e: 2025-12-04T08:11:25.8044222Z  log.error( 2025-12-04T08:11:25.8045050Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T08:11:25.8045821Z  ) 2025-12-04T08:11:25.8046312Z  2025-12-04T08:11:25.8046871Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:11:25.8047545Z  2025-12-04T08:11:25.8047911Z  2025-12-04T08:11:25.8048311Z if __name__ == "__main__": 2025-12-04T08:11:25.8048817Z  main() 2025-12-04T08:11:25.8049232Z  2025-12-04T08:11:25.8049594Z EOF 2025-12-04T08:11:25.8049969Z  2025-12-04T08:11:25.8050368Z cat runner_determinator.py 2025-12-04T08:11:26.1510208Z shell: /usr/bin/bash -e {0} 2025-12-04T08:11:26.1511314Z env: 2025-12-04T08:11:26.1512163Z GITHUB_TOKEN: *** 2025-12-04T08:11:26.1512688Z ISSUE_NUMBER: 5132 2025-12-04T08:11:26.1513250Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:11:26.1513876Z ISSUE_OWNER: 2025-12-04T08:11:26.1514589Z CHECK_EXPERIMENTS: 2025-12-04T08:11:26.1515140Z OPT_OUT_EXPERIMENTS: lf 2025-12-04T08:11:26.1515697Z PR_NUMBER: 2025-12-04T08:11:26.1516204Z ##[endgroup] 2025-12-04T08:11:26.1705439Z # flake8: noqa: G004 2025-12-04T08:11:26.1705810Z 2025-12-04T08:11:26.1706248Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T08:11:26.1707182Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T08:11:26.1708002Z # python .github/scripts/update_runner_determinator.py 2025-12-04T08:11:26.1708443Z 2025-12-04T08:11:26.1708610Z """ 2025-12-04T08:11:26.1709174Z This runner determinator is used to determine which set of runners to run a 2025-12-04T08:11:26.1710038Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T08:11:26.1710921Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T08:11:26.1711739Z of which runners should be used to run which job. 2025-12-04T08:11:26.1712137Z 2025-12-04T08:11:26.1712515Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T08:11:26.1713588Z separated by a line containing "---". If the line is not present, the 2025-12-04T08:11:26.1715089Z settings are considered to be empty with only the second part, the user 2025-12-04T08:11:26.1715794Z list, defined. 2025-12-04T08:11:26.1716033Z 2025-12-04T08:11:26.1716394Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T08:11:26.1717312Z used to define any settings that are needed to determine which runners to use. 2025-12-04T08:11:26.1718124Z It's fields are defined by the RolloutSettings class below. 2025-12-04T08:11:26.1718557Z 2025-12-04T08:11:26.1718933Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T08:11:26.1719783Z The user list is also a comma separated list of additional features or 2025-12-04T08:11:26.1720508Z experiments which the user could be opted in to. 2025-12-04T08:11:26.1720903Z 2025-12-04T08:11:26.1721101Z The user list has the following rules: 2025-12-04T08:11:26.1721455Z 2025-12-04T08:11:26.1721782Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T08:11:26.1722630Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T08:11:26.1723390Z - A "#" prefix opts the user out of all experiments 2025-12-04T08:11:26.1723779Z 2025-12-04T08:11:26.1723956Z Example config: 2025-12-04T08:11:26.1724590Z # A list of experiments that can be opted into. 2025-12-04T08:11:26.1725272Z # This defines the behavior they'll induce when opted into. 2025-12-04T08:11:26.1725892Z # Expected syntax is: 2025-12-04T08:11:26.1726540Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T08:11:26.1727501Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T08:11:26.1728110Z 2025-12-04T08:11:26.1728284Z experiments: 2025-12-04T08:11:26.1728684Z lf: 2025-12-04T08:11:26.1729066Z rollout_percent: 25 2025-12-04T08:11:26.1729659Z all_branches: false 2025-12-04T08:11:26.1730113Z default: true 2025-12-04T08:11:26.1730539Z --- 2025-12-04T08:11:26.1730747Z 2025-12-04T08:11:26.1730919Z # Opt-ins: 2025-12-04T08:11:26.1731511Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T08:11:26.1732361Z # and specifying experiments to enable in a comma-separated list. 2025-12-04T08:11:26.1733148Z # To always opt out of an experiment, prefix it with a "-". 2025-12-04T08:11:26.1733828Z # Experiments should be from the above list. 2025-12-04T08:11:26.1734204Z 2025-12-04T08:11:26.1734554Z @User1,-lf,split_build 2025-12-04T08:11:26.1735011Z @User2,lf 2025-12-04T08:11:26.1735400Z @User3,split_build 2025-12-04T08:11:26.1735823Z """ 2025-12-04T08:11:26.1736016Z 2025-12-04T08:11:26.1736181Z import json 2025-12-04T08:11:26.1736565Z import logging 2025-12-04T08:11:26.1736959Z import os 2025-12-04T08:11:26.1737341Z import random 2025-12-04T08:11:26.1737722Z import re 2025-12-04T08:11:26.1738115Z import sys 2025-12-04T08:11:26.1738536Z from argparse import ArgumentParser 2025-12-04T08:11:26.1739077Z from collections.abc import Iterable 2025-12-04T08:11:26.1739623Z from functools import cache 2025-12-04T08:11:26.1740105Z from logging import LogRecord 2025-12-04T08:11:26.1740611Z from typing import Any, NamedTuple 2025-12-04T08:11:26.1741150Z from urllib.request import Request, urlopen 2025-12-04T08:11:26.1741529Z 2025-12-04T08:11:26.1741695Z import yaml 2025-12-04T08:11:26.1742096Z from github import Auth, Github 2025-12-04T08:11:26.1742598Z from github.Issue import Issue 2025-12-04T08:11:26.1742907Z 2025-12-04T08:11:26.1742914Z 2025-12-04T08:11:26.1743146Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T08:11:26.1743839Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T08:11:26.1744881Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T08:11:26.1745434Z 2025-12-04T08:11:26.1745676Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T08:11:26.1746397Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T08:11:26.1746951Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T08:11:26.1747530Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T08:11:26.1747887Z 2025-12-04T08:11:26.1748113Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T08:11:26.1748462Z 2025-12-04T08:11:26.1748656Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T08:11:26.1749151Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T08:11:26.1749444Z 2025-12-04T08:11:26.1749451Z 2025-12-04T08:11:26.1749651Z class Experiment(NamedTuple): 2025-12-04T08:11:26.1750153Z rollout_perc: float = ( 2025-12-04T08:11:26.1750798Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T08:11:26.1751493Z ) 2025-12-04T08:11:26.1751881Z all_branches: bool = ( 2025-12-04T08:11:26.1752508Z False # If True, the experiment is also enabled on the exception branches 2025-12-04T08:11:26.1753183Z ) 2025-12-04T08:11:26.1753549Z default: bool = ( 2025-12-04T08:11:26.1754124Z True # If True, the experiment is enabled by default for all queries 2025-12-04T08:11:26.1754888Z ) 2025-12-04T08:11:26.1755091Z 2025-12-04T08:11:26.1755272Z # Add more fields as needed 2025-12-04T08:11:26.1755575Z 2025-12-04T08:11:26.1755581Z 2025-12-04T08:11:26.1755775Z class Settings(NamedTuple): 2025-12-04T08:11:26.1756217Z """ 2025-12-04T08:11:26.1756679Z Settings for the experiments that can be opted into. 2025-12-04T08:11:26.1757254Z """ 2025-12-04T08:11:26.1757458Z 2025-12-04T08:11:26.1757672Z experiments: dict[str, Experiment] = {} 2025-12-04T08:11:26.1758040Z 2025-12-04T08:11:26.1758046Z 2025-12-04T08:11:26.1758256Z class ColorFormatter(logging.Formatter): 2025-12-04T08:11:26.1758884Z """Color codes the log messages based on the log level""" 2025-12-04T08:11:26.1759314Z 2025-12-04T08:11:26.1759483Z COLORS = { 2025-12-04T08:11:26.1759885Z "WARNING": "\033[33m", # Yellow 2025-12-04T08:11:26.1760497Z "ERROR": "\033[31m", # Red 2025-12-04T08:11:26.1760993Z "CRITICAL": "\033[31m", # Red 2025-12-04T08:11:26.1761501Z "INFO": "\033[0m", # Reset 2025-12-04T08:11:26.1761984Z "DEBUG": "\033[0m", # Reset 2025-12-04T08:11:26.1762462Z } 2025-12-04T08:11:26.1762659Z 2025-12-04T08:11:26.1762879Z def format(self, record: LogRecord) -> str: 2025-12-04T08:11:26.1763623Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T08:11:26.1764504Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T08:11:26.1765085Z return super().format(record) 2025-12-04T08:11:26.1765415Z 2025-12-04T08:11:26.1765422Z 2025-12-04T08:11:26.1765630Z handler = logging.StreamHandler() 2025-12-04T08:11:26.1766330Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T08:11:26.1766882Z 2025-12-04T08:11:26.1767127Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T08:11:26.1767703Z log.addHandler(handler) 2025-12-04T08:11:26.1768161Z log.setLevel(logging.INFO) 2025-12-04T08:11:26.1768446Z 2025-12-04T08:11:26.1768453Z 2025-12-04T08:11:26.1768718Z def set_github_output(key: str, value: str) -> None: 2025-12-04T08:11:26.1769287Z """ 2025-12-04T08:11:26.1769789Z Defines outputs of the github action that invokes this script 2025-12-04T08:11:26.1770405Z """ 2025-12-04T08:11:26.1770782Z if not GITHUB_OUTPUT: 2025-12-04T08:11:26.1771838Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T08:11:26.1772949Z log.warning( 2025-12-04T08:11:26.1773789Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T08:11:26.1774847Z ) 2025-12-04T08:11:26.1784984Z print(f"::set-output name={key}::{value}") 2025-12-04T08:11:26.1785592Z return 2025-12-04T08:11:26.1785823Z 2025-12-04T08:11:26.1786242Z with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T08:11:26.1786822Z log.info(f"Setting output: {key}='{value}'") 2025-12-04T08:11:26.1787390Z f.write(f"{key}={value}\n") 2025-12-04T08:11:26.1787709Z 2025-12-04T08:11:26.1787715Z 2025-12-04T08:11:26.1788017Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T08:11:26.1788638Z return frozenset( 2025-12-04T08:11:26.1789254Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T08:11:26.1789927Z ) 2025-12-04T08:11:26.1790126Z 2025-12-04T08:11:26.1790135Z 2025-12-04T08:11:26.1790314Z def parse_args() -> Any: 2025-12-04T08:11:26.1790856Z parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T08:11:26.1791694Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T08:11:26.1792457Z parser.add_argument( 2025-12-04T08:11:26.1792910Z "--github-issue-repo", 2025-12-04T08:11:26.1793379Z type=str, 2025-12-04T08:11:26.1793781Z required=False, 2025-12-04T08:11:26.1794229Z default="pytorch/test-infra", 2025-12-04T08:11:26.1794954Z help="GitHub repo to get the issue", 2025-12-04T08:11:26.1795472Z ) 2025-12-04T08:11:26.1795835Z parser.add_argument( 2025-12-04T08:11:26.1796279Z "--github-repo", 2025-12-04T08:11:26.1796745Z type=str, 2025-12-04T08:11:26.1797140Z required=True, 2025-12-04T08:11:26.1797602Z help="GitHub repo where CI is running", 2025-12-04T08:11:26.1798118Z ) 2025-12-04T08:11:26.1798484Z parser.add_argument( 2025-12-04T08:11:26.1799070Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T08:11:26.1799718Z ) 2025-12-04T08:11:26.1800086Z parser.add_argument( 2025-12-04T08:11:26.1800702Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T08:11:26.1801374Z ) 2025-12-04T08:11:26.1801737Z parser.add_argument( 2025-12-04T08:11:26.1802474Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T08:11:26.1803163Z ) 2025-12-04T08:11:26.1803534Z parser.add_argument( 2025-12-04T08:11:26.1804182Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T08:11:26.1804979Z ) 2025-12-04T08:11:26.1805335Z parser.add_argument( 2025-12-04T08:11:26.1805783Z "--github-ref-type", 2025-12-04T08:11:26.1806239Z type=str, 2025-12-04T08:11:26.1806635Z required=True, 2025-12-04T08:11:26.1807118Z help="Current GitHub ref type, branch or tag", 2025-12-04T08:11:26.1807656Z ) 2025-12-04T08:11:26.1808032Z parser.add_argument( 2025-12-04T08:11:26.1808490Z "--eligible-experiments", 2025-12-04T08:11:26.1809005Z type=_str_comma_separated_to_set, 2025-12-04T08:11:26.1809515Z required=False, 2025-12-04T08:11:26.1809940Z default="", 2025-12-04T08:11:26.1810767Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T08:11:26.1811684Z ) 2025-12-04T08:11:26.1812050Z parser.add_argument( 2025-12-04T08:11:26.1812503Z "--opt-out-experiments", 2025-12-04T08:11:26.1813010Z type=_str_comma_separated_to_set, 2025-12-04T08:11:26.1813525Z required=False, 2025-12-04T08:11:26.1813949Z default="", 2025-12-04T08:11:26.1814401Z help=( 2025-12-04T08:11:26.1815071Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T08:11:26.1816181Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T08:11:26.1817001Z ), 2025-12-04T08:11:26.1817366Z ) 2025-12-04T08:11:26.1817730Z parser.add_argument( 2025-12-04T08:11:26.1818175Z "--pr-number", 2025-12-04T08:11:26.1818584Z type=str, 2025-12-04T08:11:26.1818984Z required=False, 2025-12-04T08:11:26.1819408Z default="", 2025-12-04T08:11:26.1819959Z help="the optional PR number where this is run", 2025-12-04T08:11:26.1820507Z ) 2025-12-04T08:11:26.1820709Z 2025-12-04T08:11:26.1820896Z return parser.parse_args() 2025-12-04T08:11:26.1821206Z 2025-12-04T08:11:26.1821212Z 2025-12-04T08:11:26.1821615Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T08:11:26.1822357Z auth = Auth.Token(github_token) 2025-12-04T08:11:26.1822867Z return Github(auth=auth) 2025-12-04T08:11:26.1823164Z 2025-12-04T08:11:26.1823172Z 2025-12-04T08:11:26.1823622Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T08:11:26.1824631Z repo = gh.get_repo(repo) 2025-12-04T08:11:26.1825137Z return repo.get_issue(number=issue_num) 2025-12-04T08:11:26.1825510Z 2025-12-04T08:11:26.1825517Z 2025-12-04T08:11:26.1825707Z def get_potential_pr_author( 2025-12-04T08:11:26.1826357Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T08:11:26.1827024Z ) -> str: 2025-12-04T08:11:26.1827545Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T08:11:26.1828335Z # Fetch the actual username from the original PR. The PR number is 2025-12-04T08:11:26.1829064Z # embedded in the tag name: ciflow// 2025-12-04T08:11:26.1829473Z 2025-12-04T08:11:26.1829659Z gh = get_gh_client(github_token) 2025-12-04T08:11:26.1830002Z 2025-12-04T08:11:26.1830266Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T08:11:26.1830879Z split_tag = ref_name.split("/") 2025-12-04T08:11:26.1831371Z if ( 2025-12-04T08:11:26.1831760Z len(split_tag) == 3 2025-12-04T08:11:26.1832234Z and split_tag[0] == "ciflow" 2025-12-04T08:11:26.1832760Z and split_tag[2].isnumeric() 2025-12-04T08:11:26.1833242Z ): 2025-12-04T08:11:26.1833624Z pr_number = split_tag[2] 2025-12-04T08:11:26.1834196Z try: 2025-12-04T08:11:26.1834695Z repository = gh.get_repo(repo) 2025-12-04T08:11:26.1835311Z pull = repository.get_pull(number=int(pr_number)) 2025-12-04T08:11:26.1835904Z except Exception as e: 2025-12-04T08:11:26.1836425Z raise Exception( # noqa: TRY002 2025-12-04T08:11:26.1837079Z f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T08:11:26.1837720Z ) from e 2025-12-04T08:11:26.1838247Z return pull.user.login # type: ignore[no-any-return] 2025-12-04T08:11:26.1838933Z # In all other cases, return the original input username 2025-12-04T08:11:26.1839509Z return username 2025-12-04T08:11:26.1839757Z 2025-12-04T08:11:26.1839763Z 2025-12-04T08:11:26.1839984Z def is_exception_branch(branch: str) -> bool: 2025-12-04T08:11:26.1840517Z """ 2025-12-04T08:11:26.1841139Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T08:11:26.1841910Z """ 2025-12-04T08:11:26.1842440Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T08:11:26.1842958Z 2025-12-04T08:11:26.1842965Z 2025-12-04T08:11:26.1843157Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T08:11:26.1843640Z try: 2025-12-04T08:11:26.1844032Z data = yaml.safe_load(yaml_text) 2025-12-04T08:11:26.1844595Z return data 2025-12-04T08:11:26.1845018Z except yaml.YAMLError: 2025-12-04T08:11:26.1845501Z log.exception("Error loading YAML") 2025-12-04T08:11:26.1846004Z raise 2025-12-04T08:11:26.1846225Z 2025-12-04T08:11:26.1846232Z 2025-12-04T08:11:26.1846658Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T08:11:26.1847377Z """ 2025-12-04T08:11:26.1847984Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T08:11:26.1848571Z 2025-12-04T08:11:26.1848997Z If the issue body contains "---" then the text above that is the settings 2025-12-04T08:11:26.1849735Z and the text below is the list of opted in users. 2025-12-04T08:11:26.1850146Z 2025-12-04T08:11:26.1850520Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T08:11:26.1851206Z """ 2025-12-04T08:11:26.1851651Z rollout_state_parts = rollout_state.split("---") 2025-12-04T08:11:26.1852241Z if len(rollout_state_parts) >= 2: 2025-12-04T08:11:26.1852849Z return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T08:11:26.1853445Z else: 2025-12-04T08:11:26.1853821Z return "", rollout_state 2025-12-04T08:11:26.1854121Z 2025-12-04T08:11:26.1854129Z 2025-12-04T08:11:26.1854393Z class UserOptins(dict[str, list[str]]): 2025-12-04T08:11:26.1854891Z """ 2025-12-04T08:11:26.1855402Z Dictionary of users with a list of features they have opted into 2025-12-04T08:11:26.1856032Z """ 2025-12-04T08:11:26.1856231Z 2025-12-04T08:11:26.1856244Z 2025-12-04T08:11:26.1856577Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T08:11:26.1857223Z """ 2025-12-04T08:11:26.1857909Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-12-04T08:11:26.1858585Z 2025-12-04T08:11:26.1859197Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T08:11:26.1860165Z - Example line: "@User1,lf,split_build" 2025-12-04T08:11:26.1860839Z - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T08:11:26.1861314Z 2025-12-04T08:11:26.1861322Z 2025-12-04T08:11:26.1861483Z """ 2025-12-04T08:11:26.1861848Z optins = UserOptins() 2025-12-04T08:11:26.1862338Z for user in user_optin_text.split("\n"): 2025-12-04T08:11:26.1862877Z user = user.strip("\r\n\t -") 2025-12-04T08:11:26.1863411Z if not user or not user.startswith("@"): 2025-12-04T08:11:26.1864027Z # Not a valid user. Skip 2025-12-04T08:11:26.1864704Z continue 2025-12-04T08:11:26.1864950Z 2025-12-04T08:11:26.1865109Z if user: 2025-12-04T08:11:26.1865550Z usr_name = user.split(",")[0].strip("@") 2025-12-04T08:11:26.1866240Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T08:11:26.1866722Z 2025-12-04T08:11:26.1866888Z return optins 2025-12-04T08:11:26.1867119Z 2025-12-04T08:11:26.1867126Z 2025-12-04T08:11:26.1867411Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T08:11:26.1867997Z """ 2025-12-04T08:11:26.1868389Z Check if the experiment name is valid. 2025-12-04T08:11:26.1868901Z A valid name: 2025-12-04T08:11:26.1869530Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T08:11:26.1870452Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T08:11:26.1871159Z - Cannot contain spaces 2025-12-04T08:11:26.1871617Z """ 2025-12-04T08:11:26.1871812Z 2025-12-04T08:11:26.1872070Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T08:11:26.1872765Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T08:11:26.1873199Z 2025-12-04T08:11:26.1873360Z if valid: 2025-12-04T08:11:26.1873744Z return True 2025-12-04T08:11:26.1873976Z 2025-12-04T08:11:26.1874140Z log.error( 2025-12-04T08:11:26.1875630Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-12-04T08:11:26.1877223Z ) 2025-12-04T08:11:26.1877579Z return False 2025-12-04T08:11:26.1877816Z 2025-12-04T08:11:26.1877823Z 2025-12-04T08:11:26.1878120Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T08:11:26.1878742Z """ 2025-12-04T08:11:26.1879414Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T08:11:26.1880128Z """ 2025-12-04T08:11:26.1880473Z try: 2025-12-04T08:11:26.1880847Z if settings_text: 2025-12-04T08:11:26.1881572Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T08:11:26.1882353Z # for easy reading 2025-12-04T08:11:26.1883133Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T08:11:26.1884017Z # the backtick character in shell commands. 2025-12-04T08:11:26.1884697Z backtick = chr(96) # backtick character 2025-12-04T08:11:26.1885350Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T08:11:26.1886005Z settings = load_yaml(settings_text) 2025-12-04T08:11:26.1886366Z 2025-12-04T08:11:26.1886763Z # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T08:11:26.1887514Z experiments = {} 2025-12-04T08:11:26.1887806Z 2025-12-04T08:11:26.1888190Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T08:11:26.1888926Z if not is_valid_experiment_name(exp_name): 2025-12-04T08:11:26.1889998Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-12-04T08:11:26.1891003Z continue 2025-12-04T08:11:26.1891292Z 2025-12-04T08:11:26.1891473Z valid_settings = {} 2025-12-04T08:11:26.1891977Z for setting in exp_settings: 2025-12-04T08:11:26.1892545Z if setting not in Experiment._fields: 2025-12-04T08:11:26.1893097Z log.warning( 2025-12-04T08:11:26.1893790Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T08:11:26.1894628Z ) 2025-12-04T08:11:26.1895050Z else: 2025-12-04T08:11:26.1895569Z valid_settings[setting] = exp_settings[setting] 2025-12-04T08:11:26.1895990Z 2025-12-04T08:11:26.1896263Z experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T08:11:26.1896888Z return Settings(experiments) 2025-12-04T08:11:26.1897226Z 2025-12-04T08:11:26.1897406Z except Exception: 2025-12-04T08:11:26.1897873Z log.exception("Failed to parse settings") 2025-12-04T08:11:26.1898250Z 2025-12-04T08:11:26.1898429Z return Settings() 2025-12-04T08:11:26.1898680Z 2025-12-04T08:11:26.1898687Z 2025-12-04T08:11:26.1898929Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T08:11:26.1899489Z """ 2025-12-04T08:11:26.1899910Z Parse settings, if any, from the rollout state. 2025-12-04T08:11:26.1900312Z 2025-12-04T08:11:26.1900651Z If the issue body contains "---" then the text above that is the settings 2025-12-04T08:11:26.1901405Z and the text below is the list of opted in users. 2025-12-04T08:11:26.1901798Z 2025-12-04T08:11:26.1902195Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T08:11:26.1902915Z """ 2025-12-04T08:11:26.1903452Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:11:26.1904197Z return parse_settings_from_text(settings_text) 2025-12-04T08:11:26.1904643Z 2025-12-04T08:11:26.1904650Z 2025-12-04T08:11:26.1904898Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T08:11:26.1905449Z """ 2025-12-04T08:11:26.1905838Z Parse users from the rollout state. 2025-12-04T08:11:26.1906181Z 2025-12-04T08:11:26.1906337Z """ 2025-12-04T08:11:26.1906865Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:11:26.1907582Z return parse_user_opt_in_from_text(users_text) 2025-12-04T08:11:26.1907986Z 2025-12-04T08:11:26.1907993Z 2025-12-04T08:11:26.1908458Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:11:26.1909202Z """ 2025-12-04T08:11:26.1909607Z Check if a user is opted into an experiment 2025-12-04T08:11:26.1910139Z """ 2025-12-04T08:11:26.1910582Z return experiment_name in user_optins.get(user, []) 2025-12-04T08:11:26.1911003Z 2025-12-04T08:11:26.1911010Z 2025-12-04T08:11:26.1911417Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:11:26.1912145Z """ 2025-12-04T08:11:26.1912592Z Check if a user explicitly opted out of an experiment 2025-12-04T08:11:26.1913162Z """ 2025-12-04T08:11:26.1913653Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T08:11:26.1914386Z experiment_optout = "-" + experiment_name 2025-12-04T08:11:26.1915016Z if experiment_optout not in user_optins.get(user, []): 2025-12-04T08:11:26.1915614Z return False 2025-12-04T08:11:26.1915871Z 2025-12-04T08:11:26.1916142Z if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T08:11:26.1916730Z log.warning( 2025-12-04T08:11:26.1917510Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T08:11:26.1918377Z ) 2025-12-04T08:11:26.1918583Z 2025-12-04T08:11:26.1918762Z return True 2025-12-04T08:11:26.1918992Z 2025-12-04T08:11:26.1918999Z 2025-12-04T08:11:26.1919172Z def get_runner_prefix( 2025-12-04T08:11:26.1919604Z rollout_state: str, 2025-12-04T08:11:26.1920051Z workflow_requestors: Iterable[str], 2025-12-04T08:11:26.1920559Z branch: str, 2025-12-04T08:11:26.1921034Z eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T08:11:26.1921684Z opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T08:11:26.1922259Z is_canary: bool = False, 2025-12-04T08:11:26.1922695Z ) -> str: 2025-12-04T08:11:26.1923187Z settings = parse_settings(rollout_state) 2025-12-04T08:11:26.1923749Z user_optins = parse_users(rollout_state) 2025-12-04T08:11:26.1924107Z 2025-12-04T08:11:26.1924342Z fleet_prefix = "" 2025-12-04T08:11:26.1924759Z prefixes = [] 2025-12-04T08:11:26.1925376Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T08:11:26.1926272Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T08:11:26.1926968Z log.info( 2025-12-04T08:11:26.1927629Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T08:11:26.1928355Z ) 2025-12-04T08:11:26.1928733Z continue 2025-12-04T08:11:26.1928978Z 2025-12-04T08:11:26.1929161Z if opt_out_experiments: 2025-12-04T08:11:26.1929681Z if experiment_name in opt_out_experiments: 2025-12-04T08:11:26.1930301Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T08:11:26.1930886Z log.info( 2025-12-04T08:11:26.1931774Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T08:11:26.1932722Z ) 2025-12-04T08:11:26.1933116Z continue 2025-12-04T08:11:26.1933378Z 2025-12-04T08:11:26.1933563Z if eligible_experiments: 2025-12-04T08:11:26.1934108Z if experiment_name not in eligible_experiments: 2025-12-04T08:11:26.1934785Z exp_list = ", ".join(eligible_experiments) 2025-12-04T08:11:26.1935336Z log.info( 2025-12-04T08:11:26.1936093Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T08:11:26.1936909Z ) 2025-12-04T08:11:26.1937306Z continue 2025-12-04T08:11:26.1937769Z elif not experiment_settings.default: 2025-12-04T08:11:26.1938527Z log.info( 2025-12-04T08:11:26.1939377Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T08:11:26.1940107Z ) 2025-12-04T08:11:26.1940475Z continue 2025-12-04T08:11:26.1940726Z 2025-12-04T08:11:26.1940996Z # Is any workflow_requestor opted out to this experiment? 2025-12-04T08:11:26.1941595Z opted_out_users = [ 2025-12-04T08:11:26.1942045Z requestor 2025-12-04T08:11:26.1942507Z for requestor in workflow_requestors 2025-12-04T08:11:26.1943164Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T08:11:26.1943785Z ] 2025-12-04T08:11:26.1943987Z 2025-12-04T08:11:26.1944169Z if opted_out_users: 2025-12-04T08:11:26.1944686Z log.info( 2025-12-04T08:11:26.1945297Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T08:11:26.1945985Z ) 2025-12-04T08:11:26.1946383Z continue 2025-12-04T08:11:26.1946645Z 2025-12-04T08:11:26.1946913Z # Is any workflow_requestor opted in to this experiment? 2025-12-04T08:11:26.1947524Z opted_in_users = [ 2025-12-04T08:11:26.1947960Z requestor 2025-12-04T08:11:26.1948413Z for requestor in workflow_requestors 2025-12-04T08:11:26.1949054Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T08:11:26.1949662Z ] 2025-12-04T08:11:26.1949859Z 2025-12-04T08:11:26.1950028Z enabled = False 2025-12-04T08:11:26.1950460Z if opted_in_users: 2025-12-04T08:11:26.1950888Z log.info( 2025-12-04T08:11:26.1951486Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T08:11:26.1952154Z ) 2025-12-04T08:11:26.1952534Z enabled = True 2025-12-04T08:11:26.1952830Z 2025-12-04T08:11:26.1953051Z elif experiment_settings.rollout_perc: 2025-12-04T08:11:26.1953859Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T08:11:26.1954938Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T08:11:26.1955571Z log.info( 2025-12-04T08:11:26.1956416Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T08:11:26.1957305Z ) 2025-12-04T08:11:26.1957702Z enabled = True 2025-12-04T08:11:26.1957990Z 2025-12-04T08:11:26.1958162Z if enabled: 2025-12-04T08:11:26.1958571Z label = experiment_name 2025-12-04T08:11:26.1959114Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T08:11:26.1959909Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T08:11:26.1960761Z # - If it's enabled, then we always list it's prefix first 2025-12-04T08:11:26.1961501Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T08:11:26.1962151Z if is_canary: 2025-12-04T08:11:26.1962635Z label += CANARY_FLEET_SUFFIX 2025-12-04T08:11:26.1963165Z fleet_prefix = label 2025-12-04T08:11:26.1963646Z else: 2025-12-04T08:11:26.1964060Z prefixes.append(label) 2025-12-04T08:11:26.1964461Z 2025-12-04T08:11:26.1964722Z if len(prefixes) > 1: 2025-12-04T08:11:26.1965353Z log.error( 2025-12-04T08:11:26.1966429Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-12-04T08:11:26.1967527Z ) 2025-12-04T08:11:26.1967902Z prefixes = prefixes[:1] 2025-12-04T08:11:26.1968204Z 2025-12-04T08:11:26.1968395Z # Fleet always comes first 2025-12-04T08:11:26.1968854Z if fleet_prefix: 2025-12-04T08:11:26.1969300Z prefixes.insert(0, fleet_prefix) 2025-12-04T08:11:26.1969651Z 2025-12-04T08:11:26.1969970Z return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T08:11:26.1970384Z 2025-12-04T08:11:26.1970392Z 2025-12-04T08:11:26.1970810Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T08:11:26.1971563Z """ 2025-12-04T08:11:26.1972127Z Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T08:11:26.1972677Z 2025-12-04T08:11:26.1973048Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T08:11:26.1973729Z """ 2025-12-04T08:11:26.1974115Z gh = get_gh_client(github_token) 2025-12-04T08:11:26.1974998Z issue = get_issue(gh, repo, issue_num) 2025-12-04T08:11:26.1975929Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T08:11:26.1976684Z 2025-12-04T08:11:26.1976696Z 2025-12-04T08:11:26.1977391Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T08:11:26.1978951Z for _ in range(num_retries): 2025-12-04T08:11:26.1979950Z try: 2025-12-04T08:11:26.1980780Z req = Request(url=url, headers=headers) 2025-12-04T08:11:26.1982043Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T08:11:26.1983382Z return json.loads(content) 2025-12-04T08:11:26.1984535Z except Exception as e: 2025-12-04T08:11:26.1985647Z log.warning(f"Could not download {url}: {e}") 2025-12-04T08:11:26.1986441Z 2025-12-04T08:11:26.1987210Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T08:11:26.1988671Z return {} 2025-12-04T08:11:26.1989124Z 2025-12-04T08:11:26.1989137Z 2025-12-04T08:11:26.1989492Z @cache 2025-12-04T08:11:26.1990665Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T08:11:26.1992222Z """ 2025-12-04T08:11:26.1992997Z Dynamically get PR information 2025-12-04T08:11:26.1993845Z """ 2025-12-04T08:11:26.1994963Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T08:11:26.1995728Z headers = { 2025-12-04T08:11:26.1996184Z "Accept": "application/vnd.github.v3+json", 2025-12-04T08:11:26.1996796Z "Authorization": f"token {github_token}", 2025-12-04T08:11:26.1997322Z } 2025-12-04T08:11:26.1997752Z json_response: dict[str, Any] = download_json( 2025-12-04T08:11:26.1998349Z url=f"{github_api}/issues/{pr_number}", 2025-12-04T08:11:26.1998907Z headers=headers, 2025-12-04T08:11:26.1999336Z ) 2025-12-04T08:11:26.1999540Z 2025-12-04T08:11:26.1999723Z if not json_response: 2025-12-04T08:11:26.2000295Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T08:11:26.2000907Z return {} 2025-12-04T08:11:26.2001141Z 2025-12-04T08:11:26.2001332Z return json_response 2025-12-04T08:11:26.2001610Z 2025-12-04T08:11:26.2001617Z 2025-12-04T08:11:26.2002010Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T08:11:26.2002749Z """ 2025-12-04T08:11:26.2003277Z Dynamically get the latest list of labels from the pull request 2025-12-04T08:11:26.2003939Z """ 2025-12-04T08:11:26.2004573Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T08:11:26.2005169Z return { 2025-12-04T08:11:26.2005742Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T08:11:26.2006422Z } 2025-12-04T08:11:26.2006628Z 2025-12-04T08:11:26.2006635Z 2025-12-04T08:11:26.2006810Z def main() -> None: 2025-12-04T08:11:26.2007215Z args = parse_args() 2025-12-04T08:11:26.2007491Z 2025-12-04T08:11:26.2007707Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T08:11:26.2008081Z 2025-12-04T08:11:26.2008276Z # Check if the PR is opt-out 2025-12-04T08:11:26.2008762Z if args.pr_number: 2025-12-04T08:11:26.2009408Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T08:11:26.2010242Z if OPT_OUT_LABEL in labels: 2025-12-04T08:11:26.2010744Z log.info( 2025-12-04T08:11:26.2011411Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T08:11:26.2012160Z ) 2025-12-04T08:11:26.2012696Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:11:26.2013351Z sys.exit() 2025-12-04T08:11:26.2013608Z 2025-12-04T08:11:26.2013781Z try: 2025-12-04T08:11:26.2014210Z rollout_state = get_rollout_state_from_issue( 2025-12-04T08:11:26.2014966Z args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T08:11:26.2015578Z ) 2025-12-04T08:11:26.2015785Z 2025-12-04T08:11:26.2015983Z username = get_potential_pr_author( 2025-12-04T08:11:26.2016506Z args.github_token, 2025-12-04T08:11:26.2016975Z args.github_repo, 2025-12-04T08:11:26.2017444Z args.github_actor, 2025-12-04T08:11:26.2017918Z args.github_ref_type, 2025-12-04T08:11:26.2018404Z args.github_branch, 2025-12-04T08:11:26.2018848Z ) 2025-12-04T08:11:26.2019046Z 2025-12-04T08:11:26.2019329Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T08:11:26.2019763Z 2025-12-04T08:11:26.2019975Z runner_label_prefix = get_runner_prefix( 2025-12-04T08:11:26.2020513Z rollout_state, 2025-12-04T08:11:26.2020978Z (args.github_issue_owner, username), 2025-12-04T08:11:26.2021511Z args.github_branch, 2025-12-04T08:11:26.2021995Z args.eligible_experiments, 2025-12-04T08:11:26.2022509Z args.opt_out_experiments, 2025-12-04T08:11:26.2023003Z is_canary, 2025-12-04T08:11:26.2023402Z ) 2025-12-04T08:11:26.2023601Z 2025-12-04T08:11:26.2023787Z except Exception as e: 2025-12-04T08:11:26.2024222Z log.error( 2025-12-04T08:11:26.2024926Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T08:11:26.2025768Z ) 2025-12-04T08:11:26.2025978Z 2025-12-04T08:11:26.2026292Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:11:26.2026767Z 2025-12-04T08:11:26.2026774Z 2025-12-04T08:11:26.2026958Z if __name__ == "__main__": 2025-12-04T08:11:26.2027386Z main() 2025-12-04T08:11:26.2027597Z 2025-12-04T08:11:26.2111236Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T08:11:26.2112103Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T08:11:26.2131516Z shell: /usr/bin/bash -e {0} 2025-12-04T08:11:26.2132008Z env: 2025-12-04T08:11:26.2132667Z GITHUB_TOKEN: *** 2025-12-04T08:11:26.2133080Z ISSUE_NUMBER: 5132 2025-12-04T08:11:26.2133532Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:11:26.2134019Z ISSUE_OWNER: 2025-12-04T08:11:26.2134529Z CHECK_EXPERIMENTS: 2025-12-04T08:11:26.2134960Z OPT_OUT_EXPERIMENTS: lf 2025-12-04T08:11:26.2135419Z PR_NUMBER: 2025-12-04T08:11:26.2135904Z ##[endgroup] 2025-12-04T08:11:27.4699300Z Defaulting to user installation because normal site-packages is not writeable 2025-12-04T08:11:28.5463618Z Collecting urllib3==1.26.18 2025-12-04T08:11:28.6001440Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-12-04T08:11:28.6319957Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.2 MB/s eta 0:00:00 2025-12-04T08:11:28.6546617Z Collecting PyGithub==2.3.0 2025-12-04T08:11:28.6651281Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-12-04T08:11:28.7123437Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-12-04T08:11:28.7226420Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.8 kB) 2025-12-04T08:11:28.7270258Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-12-04T08:11:28.7287205Z Requirement already satisfied: pyjwt>=2.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (2.7.0) 2025-12-04T08:11:28.7301665Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-12-04T08:11:28.7655772Z Collecting Deprecated (from PyGithub==2.3.0) 2025-12-04T08:11:28.7758486Z Downloading deprecated-1.3.1-py2.py3-none-any.whl.metadata (5.9 kB) 2025-12-04T08:11:28.7967696Z Requirement already satisfied: cryptography>=3.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (41.0.7) 2025-12-04T08:11:28.9187125Z Collecting cffi>=2.0.0 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T08:11:28.9292394Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-12-04T08:11:29.0761871Z Collecting wrapt<3,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-12-04T08:11:29.0870859Z Downloading wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (9.0 kB) 2025-12-04T08:11:29.1119233Z Collecting pycparser (from cffi>=2.0.0->pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T08:11:29.1218668Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-12-04T08:11:29.1530326Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-12-04T08:11:29.1651974Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 13.3 MB/s eta 0:00:00 2025-12-04T08:11:29.1767896Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-12-04T08:11:29.1890789Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 33.2 MB/s eta 0:00:00 2025-12-04T08:11:29.1993150Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-12-04T08:11:29.2154866Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 97.4 MB/s eta 0:00:00 2025-12-04T08:11:29.2256312Z Downloading deprecated-1.3.1-py2.py3-none-any.whl (11 kB) 2025-12-04T08:11:29.2382053Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-12-04T08:11:29.2431489Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 66.0 MB/s eta 0:00:00 2025-12-04T08:11:29.2541351Z Downloading wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (121 kB) 2025-12-04T08:11:29.2585626Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.5/121.5 kB 45.8 MB/s eta 0:00:00 2025-12-04T08:11:29.2685714Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-12-04T08:11:29.2730675Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 37.0 MB/s eta 0:00:00 2025-12-04T08:11:29.5402096Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-12-04T08:11:30.0375538Z Successfully installed Deprecated-1.3.1 PyGithub-2.3.0 cffi-2.0.0 pycparser-2.23 pynacl-1.6.1 urllib3-1.26.18 wrapt-2.0.1 2025-12-04T08:11:30.1253096Z ##[group]Run curr_branch="main" 2025-12-04T08:11:30.1253387Z curr_branch="main" 2025-12-04T08:11:30.1253609Z curr_ref_type="branch" 2025-12-04T08:11:30.1253854Z echo "Current branch is '$curr_branch'" 2025-12-04T08:11:30.1254123Z  2025-12-04T08:11:30.1254452Z python3 runner_determinator.py \ 2025-12-04T08:11:30.1254727Z  --github-token "$GITHUB_TOKEN" \ 2025-12-04T08:11:30.1254987Z  --github-issue "$ISSUE_NUMBER" \ 2025-12-04T08:11:30.1255228Z  --github-branch "$curr_branch" \ 2025-12-04T08:11:30.1255482Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-12-04T08:11:30.1255748Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-12-04T08:11:30.1256018Z  --github-ref-type "$curr_ref_type" \ 2025-12-04T08:11:30.1256271Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-12-04T08:11:30.1256560Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-12-04T08:11:30.1256912Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-12-04T08:11:30.1257186Z  --pr-number "${PR_NUMBER}" 2025-12-04T08:11:30.1278133Z shell: /usr/bin/bash -e {0} 2025-12-04T08:11:30.1278363Z env: 2025-12-04T08:11:30.1278857Z GITHUB_TOKEN: *** 2025-12-04T08:11:30.1279046Z ISSUE_NUMBER: 5132 2025-12-04T08:11:30.1279253Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:11:30.1279482Z ISSUE_OWNER: 2025-12-04T08:11:30.1279669Z CHECK_EXPERIMENTS: 2025-12-04T08:11:30.1279856Z OPT_OUT_EXPERIMENTS: lf 2025-12-04T08:11:30.1280052Z PR_NUMBER: 2025-12-04T08:11:30.1280213Z ##[endgroup] 2025-12-04T08:11:30.1318588Z Current branch is 'main' 2025-12-04T08:11:31.9236769Z INFO : Skipping experiment 'lf', as this workflow has opted-out (opted out experiments are: lf) 2025-12-04T08:11:31.9238324Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-12-04T08:11:31.9239120Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-12-04T08:11:31.9239739Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-12-04T08:11:31.9240246Z INFO : Setting output: label-type='' 2025-12-04T08:11:31.9579728Z Evaluate and set job outputs 2025-12-04T08:11:31.9586897Z Cleaning up orphan processes