2025-12-04T08:51:24.0032097Z Current runner version: '2.329.0' 2025-12-04T08:51:24.0055830Z ##[group]Runner Image Provisioner 2025-12-04T08:51:24.0056677Z Hosted Compute Agent 2025-12-04T08:51:24.0057244Z Version: 20251124.448 2025-12-04T08:51:24.0057807Z Commit: fda5086b43ec66ade217e5fcd18146c879571177 2025-12-04T08:51:24.0058525Z Build Date: 2025-11-24T21:16:26Z 2025-12-04T08:51:24.0059098Z ##[endgroup] 2025-12-04T08:51:24.0059660Z ##[group]Operating System 2025-12-04T08:51:24.0060167Z Ubuntu 2025-12-04T08:51:24.0060705Z 24.04.3 2025-12-04T08:51:24.0061128Z LTS 2025-12-04T08:51:24.0061567Z ##[endgroup] 2025-12-04T08:51:24.0062121Z ##[group]Runner Image 2025-12-04T08:51:24.0062647Z Image: ubuntu-24.04 2025-12-04T08:51:24.0063127Z Version: 20251126.144.1 2025-12-04T08:51:24.0064159Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251126.144/images/ubuntu/Ubuntu2404-Readme.md 2025-12-04T08:51:24.0065863Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251126.144 2025-12-04T08:51:24.0066888Z ##[endgroup] 2025-12-04T08:51:24.0067870Z ##[group]GITHUB_TOKEN Permissions 2025-12-04T08:51:24.0069941Z Contents: read 2025-12-04T08:51:24.0070462Z Metadata: read 2025-12-04T08:51:24.0070955Z ##[endgroup] 2025-12-04T08:51:24.0072966Z Secret source: Actions 2025-12-04T08:51:24.0073679Z Prepare workflow directory 2025-12-04T08:51:24.0660189Z Prepare all required actions 2025-12-04T08:51:24.0715376Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (ffd9b0fb4355e97af82fc42cf185c3ffa0fc0a32) 2025-12-04T08:51:24.0720000Z ##[group] Inputs 2025-12-04T08:51:24.0720746Z check_experiments: 2025-12-04T08:51:24.0721307Z opt_out_experiments: lf 2025-12-04T08:51:24.0721857Z triggering_actor: pytorchmergebot 2025-12-04T08:51:24.0722514Z issue_owner: 2025-12-04T08:51:24.0722984Z curr_branch: main 2025-12-04T08:51:24.0723529Z curr_ref_type: branch 2025-12-04T08:51:24.0724166Z issue_number: 5132 2025-12-04T08:51:24.0725123Z ##[endgroup] 2025-12-04T08:51:24.0725819Z Complete job name: get-default-label-prefix / runner-determinator 2025-12-04T08:51:24.7179454Z ##[group]Run cat < runner_determinator.py 2025-12-04T08:51:24.7181950Z cat < runner_determinator.py 2025-12-04T08:51:24.7182659Z # flake8: noqa: G004 2025-12-04T08:51:24.7183204Z  2025-12-04T08:51:24.7183899Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T08:51:24.7185236Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T08:51:24.7186158Z # python .github/scripts/update_runner_determinator.py 2025-12-04T08:51:24.7186827Z  2025-12-04T08:51:24.7187319Z """ 2025-12-04T08:51:24.7187983Z This runner determinator is used to determine which set of runners to run a 2025-12-04T08:51:24.7188977Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T08:51:24.7190165Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T08:51:24.7191129Z of which runners should be used to run which job. 2025-12-04T08:51:24.7191823Z  2025-12-04T08:51:24.7192483Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T08:51:24.7193485Z separated by a line containing "---". If the line is not present, the 2025-12-04T08:51:24.7194524Z settings are considered to be empty with only the second part, the user 2025-12-04T08:51:24.7195437Z list, defined. 2025-12-04T08:51:24.7195936Z  2025-12-04T08:51:24.7196547Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T08:51:24.7197640Z used to define any settings that are needed to determine which runners to use. 2025-12-04T08:51:24.7198565Z It's fields are defined by the RolloutSettings class below. 2025-12-04T08:51:24.7199254Z  2025-12-04T08:51:24.7200241Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T08:51:24.7201237Z The user list is also a comma separated list of additional features or 2025-12-04T08:51:24.7202091Z experiments which the user could be opted in to. 2025-12-04T08:51:24.7202770Z  2025-12-04T08:51:24.7203260Z The user list has the following rules: 2025-12-04T08:51:24.7203872Z  2025-12-04T08:51:24.7204908Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T08:51:24.7205889Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T08:51:24.7206742Z - A "#" prefix opts the user out of all experiments 2025-12-04T08:51:24.7207452Z  2025-12-04T08:51:24.7207871Z Example config: 2025-12-04T08:51:24.7208498Z  # A list of experiments that can be opted into. 2025-12-04T08:51:24.7209332Z  # This defines the behavior they'll induce when opted into. 2025-12-04T08:51:24.7210032Z  # Expected syntax is: 2025-12-04T08:51:24.7210806Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T08:51:24.7211877Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T08:51:24.7212732Z  2025-12-04T08:51:24.7213175Z  experiments: 2025-12-04T08:51:24.7213751Z  lf: 2025-12-04T08:51:24.7214278Z  rollout_percent: 25 2025-12-04T08:51:24.7215021Z  all_branches: false 2025-12-04T08:51:24.7215633Z  default: true 2025-12-04T08:51:24.7216152Z  --- 2025-12-04T08:51:24.7216634Z  2025-12-04T08:51:24.7217020Z  # Opt-ins: 2025-12-04T08:51:24.7217803Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T08:51:24.7218977Z  # and specifying experiments to enable in a comma-separated list. 2025-12-04T08:51:24.7219847Z  # To always opt out of an experiment, prefix it with a "-". 2025-12-04T08:51:24.7220710Z  # Experiments should be from the above list. 2025-12-04T08:51:24.7221359Z  2025-12-04T08:51:24.7221818Z  @User1,-lf,split_build 2025-12-04T08:51:24.7222423Z  @User2,lf 2025-12-04T08:51:24.7222952Z  @User3,split_build 2025-12-04T08:51:24.7223482Z """ 2025-12-04T08:51:24.7223966Z  2025-12-04T08:51:24.7224606Z import json 2025-12-04T08:51:24.7225080Z import logging 2025-12-04T08:51:24.7308358Z import os 2025-12-04T08:51:24.7309082Z import random 2025-12-04T08:51:24.7309832Z import re 2025-12-04T08:51:24.7310525Z import sys 2025-12-04T08:51:24.7311305Z from argparse import ArgumentParser 2025-12-04T08:51:24.7312329Z from collections.abc import Iterable 2025-12-04T08:51:24.7313265Z from functools import cache 2025-12-04T08:51:24.7313856Z from logging import LogRecord 2025-12-04T08:51:24.7314643Z from typing import Any, NamedTuple 2025-12-04T08:51:24.7315274Z from urllib.request import Request, urlopen 2025-12-04T08:51:24.7315818Z  2025-12-04T08:51:24.7316173Z import yaml 2025-12-04T08:51:24.7316613Z from github import Auth, Github 2025-12-04T08:51:24.7317145Z from github.Issue import Issue 2025-12-04T08:51:24.7317631Z  2025-12-04T08:51:24.7317981Z  2025-12-04T08:51:24.7318414Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T08:51:24.7319160Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T08:51:24.7320052Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T08:51:24.7320770Z  2025-12-04T08:51:24.7321481Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T08:51:24.7322079Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T08:51:24.7322636Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T08:51:24.7323220Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T08:51:24.7323742Z  2025-12-04T08:51:24.7324148Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T08:51:24.7324918Z  2025-12-04T08:51:24.7325293Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T08:51:24.7325800Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T08:51:24.7326261Z  2025-12-04T08:51:24.7326598Z  2025-12-04T08:51:24.7326986Z class Experiment(NamedTuple): 2025-12-04T08:51:24.7327510Z  rollout_perc: float = ( 2025-12-04T08:51:24.7328207Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T08:51:24.7328881Z  ) 2025-12-04T08:51:24.7329276Z  all_branches: bool = ( 2025-12-04T08:51:24.7329969Z  False # If True, the experiment is also enabled on the exception branches 2025-12-04T08:51:24.7330652Z  ) 2025-12-04T08:51:24.7331040Z  default: bool = ( 2025-12-04T08:51:24.7331656Z  True # If True, the experiment is enabled by default for all queries 2025-12-04T08:51:24.7332302Z  ) 2025-12-04T08:51:24.7332664Z  2025-12-04T08:51:24.7333046Z  # Add more fields as needed 2025-12-04T08:51:24.7333525Z  2025-12-04T08:51:24.7333873Z  2025-12-04T08:51:24.7334244Z class Settings(NamedTuple): 2025-12-04T08:51:24.7334918Z  """ 2025-12-04T08:51:24.7335427Z  Settings for the experiments that can be opted into. 2025-12-04T08:51:24.7336011Z  """ 2025-12-04T08:51:24.7336389Z  2025-12-04T08:51:24.7336803Z  experiments: dict[str, Experiment] = {} 2025-12-04T08:51:24.7337332Z  2025-12-04T08:51:24.7337822Z  2025-12-04T08:51:24.7338249Z class ColorFormatter(logging.Formatter): 2025-12-04T08:51:24.7338901Z  """Color codes the log messages based on the log level""" 2025-12-04T08:51:24.7339491Z  2025-12-04T08:51:24.7339842Z  COLORS = { 2025-12-04T08:51:24.7340301Z  "WARNING": "\033[33m", # Yellow 2025-12-04T08:51:24.7340834Z  "ERROR": "\033[31m", # Red 2025-12-04T08:51:24.7341345Z  "CRITICAL": "\033[31m", # Red 2025-12-04T08:51:24.7341880Z  "INFO": "\033[0m", # Reset 2025-12-04T08:51:24.7342394Z  "DEBUG": "\033[0m", # Reset 2025-12-04T08:51:24.7342881Z  } 2025-12-04T08:51:24.7343248Z  2025-12-04T08:51:24.7343669Z  def format(self, record: LogRecord) -> str: 2025-12-04T08:51:24.7344631Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T08:51:24.7345431Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T08:51:24.7346025Z  return super().format(record) 2025-12-04T08:51:24.7346519Z  2025-12-04T08:51:24.7346861Z  2025-12-04T08:51:24.7347253Z handler = logging.StreamHandler() 2025-12-04T08:51:24.7348003Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T08:51:24.7348720Z  2025-12-04T08:51:24.7349175Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T08:51:24.7349783Z log.addHandler(handler) 2025-12-04T08:51:24.7350265Z log.setLevel(logging.INFO) 2025-12-04T08:51:24.7350732Z  2025-12-04T08:51:24.7351064Z  2025-12-04T08:51:24.7351539Z def set_github_output(key: str, value: str) -> None: 2025-12-04T08:51:24.7352119Z  """ 2025-12-04T08:51:24.7352654Z  Defines outputs of the github action that invokes this script 2025-12-04T08:51:24.7353422Z  """ 2025-12-04T08:51:24.7353814Z  if not GITHUB_OUTPUT: 2025-12-04T08:51:24.7355085Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T08:51:24.7356177Z  log.warning( 2025-12-04T08:51:24.7357052Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T08:51:24.7357955Z  ) 2025-12-04T08:51:24.7358408Z  print(f"::set-output name={key}::{value}") 2025-12-04T08:51:24.7358958Z  return 2025-12-04T08:51:24.7359356Z  2025-12-04T08:51:24.7359758Z  with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T08:51:24.7360351Z  log.info(f"Setting output: {key}='{value}'") 2025-12-04T08:51:24.7360932Z  f.write(f"{key}={value}\n") 2025-12-04T08:51:24.7361430Z  2025-12-04T08:51:24.7361774Z  2025-12-04T08:51:24.7362292Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T08:51:24.7362932Z  return frozenset( 2025-12-04T08:51:24.7363579Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T08:51:24.7364243Z  ) 2025-12-04T08:51:24.7364838Z  2025-12-04T08:51:24.7365182Z  2025-12-04T08:51:24.7365562Z def parse_args() -> Any: 2025-12-04T08:51:24.7366170Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T08:51:24.7367047Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T08:51:24.7367801Z  parser.add_argument( 2025-12-04T08:51:24.7368290Z  "--github-issue-repo", 2025-12-04T08:51:24.7368791Z  type=str, 2025-12-04T08:51:24.7369230Z  required=False, 2025-12-04T08:51:24.7369862Z  default="pytorch/test-infra", 2025-12-04T08:51:24.7370445Z  help="GitHub repo to get the issue", 2025-12-04T08:51:24.7370963Z  ) 2025-12-04T08:51:24.7371357Z  parser.add_argument( 2025-12-04T08:51:24.7371834Z  "--github-repo", 2025-12-04T08:51:24.7372299Z  type=str, 2025-12-04T08:51:24.7372734Z  required=True, 2025-12-04T08:51:24.7373251Z  help="GitHub repo where CI is running", 2025-12-04T08:51:24.7373777Z  ) 2025-12-04T08:51:24.7374168Z  parser.add_argument( 2025-12-04T08:51:24.7375014Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T08:51:24.7375675Z  ) 2025-12-04T08:51:24.7376072Z  parser.add_argument( 2025-12-04T08:51:24.7376733Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T08:51:24.7377426Z  ) 2025-12-04T08:51:24.7377814Z  parser.add_argument( 2025-12-04T08:51:24.7378490Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T08:51:24.7379174Z  ) 2025-12-04T08:51:24.7379564Z  parser.add_argument( 2025-12-04T08:51:24.7380267Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T08:51:24.7380964Z  ) 2025-12-04T08:51:24.7381357Z  parser.add_argument( 2025-12-04T08:51:24.7381835Z  "--github-ref-type", 2025-12-04T08:51:24.7382326Z  type=str, 2025-12-04T08:51:24.7382765Z  required=True, 2025-12-04T08:51:24.7383312Z  help="Current GitHub ref type, branch or tag", 2025-12-04T08:51:24.7383859Z  ) 2025-12-04T08:51:24.7384251Z  parser.add_argument( 2025-12-04T08:51:24.7385345Z  "--eligible-experiments", 2025-12-04T08:51:24.7385917Z  type=_str_comma_separated_to_set, 2025-12-04T08:51:24.7386447Z  required=False, 2025-12-04T08:51:24.7386914Z  default="", 2025-12-04T08:51:24.7387793Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T08:51:24.7388702Z  ) 2025-12-04T08:51:24.7389093Z  parser.add_argument( 2025-12-04T08:51:24.7389617Z  "--opt-out-experiments", 2025-12-04T08:51:24.7390165Z  type=_str_comma_separated_to_set, 2025-12-04T08:51:24.7390696Z  required=False, 2025-12-04T08:51:24.7391163Z  default="", 2025-12-04T08:51:24.7391605Z  help=( 2025-12-04T08:51:24.7392306Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T08:51:24.7393429Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T08:51:24.7394248Z  ), 2025-12-04T08:51:24.7394830Z  ) 2025-12-04T08:51:24.7395229Z  parser.add_argument( 2025-12-04T08:51:24.7395709Z  "--pr-number", 2025-12-04T08:51:24.7396166Z  type=str, 2025-12-04T08:51:24.7396610Z  required=False, 2025-12-04T08:51:24.7397084Z  default="", 2025-12-04T08:51:24.7397608Z  help="the optional PR number where this is run", 2025-12-04T08:51:24.7398179Z  ) 2025-12-04T08:51:24.7398541Z  2025-12-04T08:51:24.7398926Z  return parser.parse_args() 2025-12-04T08:51:24.7399410Z  2025-12-04T08:51:24.7399852Z  2025-12-04T08:51:24.7400512Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T08:51:24.7401438Z  auth = Auth.Token(github_token) 2025-12-04T08:51:24.7401979Z  return Github(auth=auth) 2025-12-04T08:51:24.7402445Z  2025-12-04T08:51:24.7402791Z  2025-12-04T08:51:24.7403445Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T08:51:24.7404235Z  repo = gh.get_repo(repo) 2025-12-04T08:51:24.7404993Z  return repo.get_issue(number=issue_num) 2025-12-04T08:51:24.7405521Z  2025-12-04T08:51:24.7405861Z  2025-12-04T08:51:24.7406225Z def get_potential_pr_author( 2025-12-04T08:51:24.7406905Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T08:51:24.7407573Z ) -> str: 2025-12-04T08:51:24.7408124Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T08:51:24.7408936Z  # Fetch the actual username from the original PR. The PR number is 2025-12-04T08:51:24.7409707Z  # embedded in the tag name: ciflow// 2025-12-04T08:51:24.7410275Z  2025-12-04T08:51:24.7410665Z  gh = get_gh_client(github_token) 2025-12-04T08:51:24.7411162Z  2025-12-04T08:51:24.7411635Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T08:51:24.7412275Z  split_tag = ref_name.split("/") 2025-12-04T08:51:24.7412778Z  if ( 2025-12-04T08:51:24.7413201Z  len(split_tag) == 3 2025-12-04T08:51:24.7413760Z  and split_tag[0] == "ciflow" 2025-12-04T08:51:24.7414303Z  and split_tag[2].isnumeric() 2025-12-04T08:51:24.7415492Z  ): 2025-12-04T08:51:24.7415922Z  pr_number = split_tag[2] 2025-12-04T08:51:24.7416422Z  try: 2025-12-04T08:51:24.7416885Z  repository = gh.get_repo(repo) 2025-12-04T08:51:24.7417673Z  pull = repository.get_pull(number=int(pr_number)) 2025-12-04T08:51:24.7418291Z  except Exception as e: 2025-12-04T08:51:24.7418838Z  raise Exception( # noqa: TRY002 2025-12-04T08:51:24.7419525Z  f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T08:51:24.7420169Z  ) from e 2025-12-04T08:51:24.7420767Z  return pull.user.login # type: ignore[no-any-return] 2025-12-04T08:51:24.7421488Z  # In all other cases, return the original input username 2025-12-04T08:51:24.7422080Z  return username 2025-12-04T08:51:24.7422511Z  2025-12-04T08:51:24.7422851Z  2025-12-04T08:51:24.7423285Z def is_exception_branch(branch: str) -> bool: 2025-12-04T08:51:24.7423817Z  """ 2025-12-04T08:51:24.7424602Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T08:51:24.7425386Z  """ 2025-12-04T08:51:24.7425956Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T08:51:24.7426616Z  2025-12-04T08:51:24.7426953Z  2025-12-04T08:51:24.7427350Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T08:51:24.7427861Z  try: 2025-12-04T08:51:24.7428279Z  data = yaml.safe_load(yaml_text) 2025-12-04T08:51:24.7428794Z  return data 2025-12-04T08:51:24.7429249Z  except yaml.YAMLError: 2025-12-04T08:51:24.7429777Z  log.exception("Error loading YAML") 2025-12-04T08:51:24.7430290Z  raise 2025-12-04T08:51:24.7430684Z  2025-12-04T08:51:24.7431018Z  2025-12-04T08:51:24.7431626Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T08:51:24.7432359Z  """ 2025-12-04T08:51:24.7433141Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T08:51:24.7433880Z  2025-12-04T08:51:24.7434526Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T08:51:24.7435296Z  and the text below is the list of opted in users. 2025-12-04T08:51:24.7435850Z  2025-12-04T08:51:24.7436419Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T08:51:24.7437110Z  """ 2025-12-04T08:51:24.7437579Z  rollout_state_parts = rollout_state.split("---") 2025-12-04T08:51:24.7438176Z  if len(rollout_state_parts) >= 2: 2025-12-04T08:51:24.7438806Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T08:51:24.7439399Z  else: 2025-12-04T08:51:24.7439800Z  return "", rollout_state 2025-12-04T08:51:24.7440283Z  2025-12-04T08:51:24.7440615Z  2025-12-04T08:51:24.7441253Z class UserOptins(dict[str, list[str]]): 2025-12-04T08:51:24.7441761Z  """ 2025-12-04T08:51:24.7442300Z  Dictionary of users with a list of features they have opted into 2025-12-04T08:51:24.7442935Z  """ 2025-12-04T08:51:24.7443291Z  2025-12-04T08:51:24.7443617Z  2025-12-04T08:51:24.7444142Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T08:51:24.7444893Z  """ 2025-12-04T08:51:24.7445616Z  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:51:24.7446427Z  2025-12-04T08:51:24.7447220Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T08:51:24.7448199Z  - Example line: "@User1,lf,split_build" 2025-12-04T08:51:24.7449029Z  - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T08:51:24.7449648Z  2025-12-04T08:51:24.7449983Z  2025-12-04T08:51:24.7450312Z  """ 2025-12-04T08:51:24.7450701Z  optins = UserOptins() 2025-12-04T08:51:24.7451229Z  for user in user_optin_text.split("\n"): 2025-12-04T08:51:24.7451799Z  user = user.strip("\r\n\t -") 2025-12-04T08:51:24.7452373Z  if not user or not user.startswith("@"): 2025-12-04T08:51:24.7452935Z  # Not a valid user. Skip 2025-12-04T08:51:24.7453436Z  continue 2025-12-04T08:51:24.7453850Z  2025-12-04T08:51:24.7454194Z  if user: 2025-12-04T08:51:24.7454765Z  usr_name = user.split(",")[0].strip("@") 2025-12-04T08:51:24.7455466Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T08:51:24.7456124Z  2025-12-04T08:51:24.7456483Z  return optins 2025-12-04T08:51:24.7456902Z  2025-12-04T08:51:24.7457229Z  2025-12-04T08:51:24.7457728Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T08:51:24.7458330Z  """ 2025-12-04T08:51:24.7458755Z  Check if the experiment name is valid. 2025-12-04T08:51:24.7459275Z  A valid name: 2025-12-04T08:51:24.7459947Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T08:51:24.7460880Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T08:51:24.7461576Z  - Cannot contain spaces 2025-12-04T08:51:24.7462049Z  """ 2025-12-04T08:51:24.7462404Z  2025-12-04T08:51:24.7462863Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T08:51:24.7463563Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T08:51:24.7464289Z  2025-12-04T08:51:24.7464739Z  if valid: 2025-12-04T08:51:24.7465148Z  return True 2025-12-04T08:51:24.7465564Z  2025-12-04T08:51:24.7465909Z  log.error( 2025-12-04T08:51:24.7467320Z  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:51:24.7468790Z  ) 2025-12-04T08:51:24.7469157Z  return False 2025-12-04T08:51:24.7469566Z  2025-12-04T08:51:24.7469889Z  2025-12-04T08:51:24.7470409Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T08:51:24.7471038Z  """ 2025-12-04T08:51:24.7471649Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T08:51:24.7472344Z  """ 2025-12-04T08:51:24.7472717Z  try: 2025-12-04T08:51:24.7473102Z  if settings_text: 2025-12-04T08:51:24.7473841Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T08:51:24.7474715Z  # for easy reading 2025-12-04T08:51:24.7475514Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T08:51:24.7476393Z  # the backtick character in shell commands. 2025-12-04T08:51:24.7477004Z  backtick = chr(96) # backtick character 2025-12-04T08:51:24.7477675Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T08:51:24.7478339Z  settings = load_yaml(settings_text) 2025-12-04T08:51:24.7478837Z  2025-12-04T08:51:24.7479428Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T08:51:24.7480279Z  experiments = {} 2025-12-04T08:51:24.7480750Z  2025-12-04T08:51:24.7481307Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T08:51:24.7482054Z  if not is_valid_experiment_name(exp_name): 2025-12-04T08:51:24.7483129Z  # 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:51:24.7484133Z  continue 2025-12-04T08:51:24.7484672Z  2025-12-04T08:51:24.7485051Z  valid_settings = {} 2025-12-04T08:51:24.7485583Z  for setting in exp_settings: 2025-12-04T08:51:24.7486162Z  if setting not in Experiment._fields: 2025-12-04T08:51:24.7486721Z  log.warning( 2025-12-04T08:51:24.7487443Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T08:51:24.7488133Z  ) 2025-12-04T08:51:24.7488582Z  else: 2025-12-04T08:51:24.7489123Z  valid_settings[setting] = exp_settings[setting] 2025-12-04T08:51:24.7489680Z  2025-12-04T08:51:24.7490152Z  experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T08:51:24.7490793Z  return Settings(experiments) 2025-12-04T08:51:24.7491294Z  2025-12-04T08:51:24.7491649Z  except Exception: 2025-12-04T08:51:24.7492169Z  log.exception("Failed to parse settings") 2025-12-04T08:51:24.7492698Z  2025-12-04T08:51:24.7493046Z  return Settings() 2025-12-04T08:51:24.7493473Z  2025-12-04T08:51:24.7493806Z  2025-12-04T08:51:24.7494468Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T08:51:24.7495044Z  """ 2025-12-04T08:51:24.7495507Z  Parse settings, if any, from the rollout state. 2025-12-04T08:51:24.7496047Z  2025-12-04T08:51:24.7496584Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T08:51:24.7497343Z  and the text below is the list of opted in users. 2025-12-04T08:51:24.7497886Z  2025-12-04T08:51:24.7498475Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T08:51:24.7499188Z  """ 2025-12-04T08:51:24.7499759Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:51:24.7500519Z  return parse_settings_from_text(settings_text) 2025-12-04T08:51:24.7501059Z  2025-12-04T08:51:24.7501394Z  2025-12-04T08:51:24.7501842Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T08:51:24.7502405Z  """ 2025-12-04T08:51:24.7502819Z  Parse users from the rollout state. 2025-12-04T08:51:24.7503320Z  2025-12-04T08:51:24.7503646Z  """ 2025-12-04T08:51:24.7504200Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:51:24.7505037Z  return parse_user_opt_in_from_text(users_text) 2025-12-04T08:51:24.7505584Z  2025-12-04T08:51:24.7505921Z  2025-12-04T08:51:24.7506526Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:51:24.7507261Z  """ 2025-12-04T08:51:24.7507701Z  Check if a user is opted into an experiment 2025-12-04T08:51:24.7508240Z  """ 2025-12-04T08:51:24.7508713Z  return experiment_name in user_optins.get(user, []) 2025-12-04T08:51:24.7509474Z  2025-12-04T08:51:24.7509817Z  2025-12-04T08:51:24.7510429Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:51:24.7511170Z  """ 2025-12-04T08:51:24.7511657Z  Check if a user explicitly opted out of an experiment 2025-12-04T08:51:24.7512229Z  """ 2025-12-04T08:51:24.7512750Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T08:51:24.7513450Z  experiment_optout = "-" + experiment_name 2025-12-04T08:51:24.7514138Z  if experiment_optout not in user_optins.get(user, []): 2025-12-04T08:51:24.7514824Z  return False 2025-12-04T08:51:24.7515248Z  2025-12-04T08:51:24.7515706Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T08:51:24.7516299Z  log.warning( 2025-12-04T08:51:24.7517115Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T08:51:24.7517958Z  ) 2025-12-04T08:51:24.7518332Z  2025-12-04T08:51:24.7518677Z  return True 2025-12-04T08:51:24.7519082Z  2025-12-04T08:51:24.7519407Z  2025-12-04T08:51:24.7519761Z def get_runner_prefix( 2025-12-04T08:51:24.7520220Z  rollout_state: str, 2025-12-04T08:51:24.7520714Z  workflow_requestors: Iterable[str], 2025-12-04T08:51:24.7521228Z  branch: str, 2025-12-04T08:51:24.7521762Z  eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T08:51:24.7522442Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T08:51:24.7523018Z  is_canary: bool = False, 2025-12-04T08:51:24.7523484Z ) -> str: 2025-12-04T08:51:24.7523928Z  settings = parse_settings(rollout_state) 2025-12-04T08:51:24.7524609Z  user_optins = parse_users(rollout_state) 2025-12-04T08:51:24.7525134Z  2025-12-04T08:51:24.7525610Z  fleet_prefix = "" 2025-12-04T08:51:24.7526056Z  prefixes = [] 2025-12-04T08:51:24.7526714Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T08:51:24.7527648Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T08:51:24.7528335Z  log.info( 2025-12-04T08:51:24.7529030Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T08:51:24.7529756Z  ) 2025-12-04T08:51:24.7530179Z  continue 2025-12-04T08:51:24.7530601Z  2025-12-04T08:51:24.7530968Z  if opt_out_experiments: 2025-12-04T08:51:24.7531527Z  if experiment_name in opt_out_experiments: 2025-12-04T08:51:24.7532163Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T08:51:24.7532750Z  log.info( 2025-12-04T08:51:24.7533666Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T08:51:24.7534708Z  ) 2025-12-04T08:51:24.7535134Z  continue 2025-12-04T08:51:24.7535563Z  2025-12-04T08:51:24.7535941Z  if eligible_experiments: 2025-12-04T08:51:24.7536518Z  if experiment_name not in eligible_experiments: 2025-12-04T08:51:24.7537164Z  exp_list = ", ".join(eligible_experiments) 2025-12-04T08:51:24.7537708Z  log.info( 2025-12-04T08:51:24.7538497Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T08:51:24.7539303Z  ) 2025-12-04T08:51:24.7539846Z  continue 2025-12-04T08:51:24.7540357Z  elif not experiment_settings.default: 2025-12-04T08:51:24.7540879Z  log.info( 2025-12-04T08:51:24.7541559Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T08:51:24.7542268Z  ) 2025-12-04T08:51:24.7542664Z  continue 2025-12-04T08:51:24.7543077Z  2025-12-04T08:51:24.7543535Z  # Is any workflow_requestor opted out to this experiment? 2025-12-04T08:51:24.7544144Z  opted_out_users = [ 2025-12-04T08:51:24.7544712Z  requestor 2025-12-04T08:51:24.7545202Z  for requestor in workflow_requestors 2025-12-04T08:51:24.7545872Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T08:51:24.7546491Z  ] 2025-12-04T08:51:24.7546906Z  2025-12-04T08:51:24.7547315Z  if opted_out_users: 2025-12-04T08:51:24.7547793Z  log.info( 2025-12-04T08:51:24.7548429Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T08:51:24.7549112Z  ) 2025-12-04T08:51:24.7549508Z  continue 2025-12-04T08:51:24.7549920Z  2025-12-04T08:51:24.7550380Z  # Is any workflow_requestor opted in to this experiment? 2025-12-04T08:51:24.7550981Z  opted_in_users = [ 2025-12-04T08:51:24.7551448Z  requestor 2025-12-04T08:51:24.7551936Z  for requestor in workflow_requestors 2025-12-04T08:51:24.7552599Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T08:51:24.7553206Z  ] 2025-12-04T08:51:24.7553575Z  2025-12-04T08:51:24.7553925Z  enabled = False 2025-12-04T08:51:24.7554494Z  if opted_in_users: 2025-12-04T08:51:24.7555081Z  log.info( 2025-12-04T08:51:24.7555710Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T08:51:24.7556374Z  ) 2025-12-04T08:51:24.7556773Z  enabled = True 2025-12-04T08:51:24.7557228Z  2025-12-04T08:51:24.7557627Z  elif experiment_settings.rollout_perc: 2025-12-04T08:51:24.7558447Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T08:51:24.7559363Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T08:51:24.7560000Z  log.info( 2025-12-04T08:51:24.7560872Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T08:51:24.7561746Z  ) 2025-12-04T08:51:24.7562191Z  enabled = True 2025-12-04T08:51:24.7562653Z  2025-12-04T08:51:24.7563006Z  if enabled: 2025-12-04T08:51:24.7563467Z  label = experiment_name 2025-12-04T08:51:24.7564023Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T08:51:24.7564943Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T08:51:24.7565793Z  # - If it's enabled, then we always list it's prefix first 2025-12-04T08:51:24.7566557Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T08:51:24.7567214Z  if is_canary: 2025-12-04T08:51:24.7567724Z  label += CANARY_FLEET_SUFFIX 2025-12-04T08:51:24.7568268Z  fleet_prefix = label 2025-12-04T08:51:24.7568756Z  else: 2025-12-04T08:51:24.7569345Z  prefixes.append(label) 2025-12-04T08:51:24.7569834Z  2025-12-04T08:51:24.7570196Z  if len(prefixes) > 1: 2025-12-04T08:51:24.7570670Z  log.error( 2025-12-04T08:51:24.7571690Z  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:51:24.7572752Z  ) 2025-12-04T08:51:24.7573148Z  prefixes = prefixes[:1] 2025-12-04T08:51:24.7573622Z  2025-12-04T08:51:24.7573979Z  # Fleet always comes first 2025-12-04T08:51:24.7574563Z  if fleet_prefix: 2025-12-04T08:51:24.7575040Z  prefixes.insert(0, fleet_prefix) 2025-12-04T08:51:24.7575536Z  2025-12-04T08:51:24.7575983Z  return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T08:51:24.7576544Z  2025-12-04T08:51:24.7576879Z  2025-12-04T08:51:24.7577507Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T08:51:24.7578254Z  """ 2025-12-04T08:51:24.7578842Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T08:51:24.7579536Z  2025-12-04T08:51:24.7580111Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T08:51:24.7580790Z  """ 2025-12-04T08:51:24.7581197Z  gh = get_gh_client(github_token) 2025-12-04T08:51:24.7581750Z  issue = get_issue(gh, repo, issue_num) 2025-12-04T08:51:24.7582395Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T08:51:24.7582970Z  2025-12-04T08:51:24.7583305Z  2025-12-04T08:51:24.7583893Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T08:51:24.7584851Z  for _ in range(num_retries): 2025-12-04T08:51:24.7585345Z  try: 2025-12-04T08:51:24.7585789Z  req = Request(url=url, headers=headers) 2025-12-04T08:51:24.7586447Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T08:51:24.7587078Z  return json.loads(content) 2025-12-04T08:51:24.7587614Z  except Exception as e: 2025-12-04T08:51:24.7588178Z  log.warning(f"Could not download {url}: {e}") 2025-12-04T08:51:24.7588715Z  2025-12-04T08:51:24.7589283Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T08:51:24.7589984Z  return {} 2025-12-04T08:51:24.7590376Z  2025-12-04T08:51:24.7590706Z  2025-12-04T08:51:24.7591041Z @cache 2025-12-04T08:51:24.7591676Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T08:51:24.7592409Z  """ 2025-12-04T08:51:24.7592815Z  Dynamically get PR information 2025-12-04T08:51:24.7593303Z  """ 2025-12-04T08:51:24.7593825Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T08:51:24.7594526Z  headers = { 2025-12-04T08:51:24.7595029Z  "Accept": "application/vnd.github.v3+json", 2025-12-04T08:51:24.7595648Z  "Authorization": f"token {github_token}", 2025-12-04T08:51:24.7596168Z  } 2025-12-04T08:51:24.7596620Z  json_response: dict[str, Any] = download_json( 2025-12-04T08:51:24.7597220Z  url=f"{github_api}/issues/{pr_number}", 2025-12-04T08:51:24.7597759Z  headers=headers, 2025-12-04T08:51:24.7598198Z  ) 2025-12-04T08:51:24.7598556Z  2025-12-04T08:51:24.7598914Z  if not json_response: 2025-12-04T08:51:24.7599513Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T08:51:24.7600255Z  return {} 2025-12-04T08:51:24.7600658Z  2025-12-04T08:51:24.7601020Z  return json_response 2025-12-04T08:51:24.7601458Z  2025-12-04T08:51:24.7601791Z  2025-12-04T08:51:24.7602366Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T08:51:24.7603070Z  """ 2025-12-04T08:51:24.7603608Z  Dynamically get the latest list of labels from the pull request 2025-12-04T08:51:24.7604246Z  """ 2025-12-04T08:51:24.7604838Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T08:51:24.7605435Z  return { 2025-12-04T08:51:24.7606027Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T08:51:24.7606705Z  } 2025-12-04T08:51:24.7607058Z  2025-12-04T08:51:24.7607388Z  2025-12-04T08:51:24.7607743Z def main() -> None: 2025-12-04T08:51:24.7608184Z  args = parse_args() 2025-12-04T08:51:24.7608628Z  2025-12-04T08:51:24.7609042Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T08:51:24.7609569Z  2025-12-04T08:51:24.7609939Z  # Check if the PR is opt-out 2025-12-04T08:51:24.7610436Z  if args.pr_number: 2025-12-04T08:51:24.7611119Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T08:51:24.7611849Z  if OPT_OUT_LABEL in labels: 2025-12-04T08:51:24.7612355Z  log.info( 2025-12-04T08:51:24.7613062Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T08:51:24.7613825Z  ) 2025-12-04T08:51:24.7614492Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:51:24.7615160Z  sys.exit() 2025-12-04T08:51:24.7615704Z  2025-12-04T08:51:24.7616045Z  try: 2025-12-04T08:51:24.7616506Z  rollout_state = get_rollout_state_from_issue( 2025-12-04T08:51:24.7617207Z  args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T08:51:24.7617828Z  ) 2025-12-04T08:51:24.7618205Z  2025-12-04T08:51:24.7618600Z  username = get_potential_pr_author( 2025-12-04T08:51:24.7619135Z  args.github_token, 2025-12-04T08:51:24.7619634Z  args.github_repo, 2025-12-04T08:51:24.7620125Z  args.github_actor, 2025-12-04T08:51:24.7620626Z  args.github_ref_type, 2025-12-04T08:51:24.7621133Z  args.github_branch, 2025-12-04T08:51:24.7621601Z  ) 2025-12-04T08:51:24.7621963Z  2025-12-04T08:51:24.7622445Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T08:51:24.7623035Z  2025-12-04T08:51:24.7623442Z  runner_label_prefix = get_runner_prefix( 2025-12-04T08:51:24.7623981Z  rollout_state, 2025-12-04T08:51:24.7624588Z  (args.github_issue_owner, username), 2025-12-04T08:51:24.7625141Z  args.github_branch, 2025-12-04T08:51:24.7625656Z  args.eligible_experiments, 2025-12-04T08:51:24.7626203Z  args.opt_out_experiments, 2025-12-04T08:51:24.7626704Z  is_canary, 2025-12-04T08:51:24.7627134Z  ) 2025-12-04T08:51:24.7627495Z  2025-12-04T08:51:24.7627862Z  except Exception as e: 2025-12-04T08:51:24.7628323Z  log.error( 2025-12-04T08:51:24.7629014Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T08:51:24.7629857Z  ) 2025-12-04T08:51:24.7630226Z  2025-12-04T08:51:24.7630750Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:51:24.7631387Z  2025-12-04T08:51:24.7631721Z  2025-12-04T08:51:24.7632070Z if __name__ == "__main__": 2025-12-04T08:51:24.7632526Z  main() 2025-12-04T08:51:24.7632895Z  2025-12-04T08:51:24.7633233Z EOF 2025-12-04T08:51:24.7633580Z  2025-12-04T08:51:24.7633947Z cat runner_determinator.py 2025-12-04T08:51:24.8221336Z shell: /usr/bin/bash -e {0} 2025-12-04T08:51:24.8222202Z env: 2025-12-04T08:51:24.8222834Z GITHUB_TOKEN: *** 2025-12-04T08:51:24.8223245Z ISSUE_NUMBER: 5132 2025-12-04T08:51:24.8223682Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:51:24.8224181Z ISSUE_OWNER: 2025-12-04T08:51:24.8224808Z CHECK_EXPERIMENTS: 2025-12-04T08:51:24.8225237Z OPT_OUT_EXPERIMENTS: lf 2025-12-04T08:51:24.8225673Z PR_NUMBER: 2025-12-04T08:51:24.8226056Z ##[endgroup] 2025-12-04T08:51:24.8427647Z # flake8: noqa: G004 2025-12-04T08:51:24.8427968Z 2025-12-04T08:51:24.8428385Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T08:51:24.8429272Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T08:51:24.8430021Z # python .github/scripts/update_runner_determinator.py 2025-12-04T08:51:24.8430431Z 2025-12-04T08:51:24.8430579Z """ 2025-12-04T08:51:24.8431125Z This runner determinator is used to determine which set of runners to run a 2025-12-04T08:51:24.8431943Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T08:51:24.8432799Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T08:51:24.8433565Z of which runners should be used to run which job. 2025-12-04T08:51:24.8433932Z 2025-12-04T08:51:24.8434295Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T08:51:24.8435655Z separated by a line containing "---". If the line is not present, the 2025-12-04T08:51:24.8436491Z settings are considered to be empty with only the second part, the user 2025-12-04T08:51:24.8437121Z list, defined. 2025-12-04T08:51:24.8437339Z 2025-12-04T08:51:24.8437687Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T08:51:24.8438540Z used to define any settings that are needed to determine which runners to use. 2025-12-04T08:51:24.8439318Z It's fields are defined by the RolloutSettings class below. 2025-12-04T08:51:24.8439733Z 2025-12-04T08:51:24.8440082Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T08:51:24.8440887Z The user list is also a comma separated list of additional features or 2025-12-04T08:51:24.8441571Z experiments which the user could be opted in to. 2025-12-04T08:51:24.8441945Z 2025-12-04T08:51:24.8442135Z The user list has the following rules: 2025-12-04T08:51:24.8442477Z 2025-12-04T08:51:24.8442772Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T08:51:24.8443563Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T08:51:24.8444279Z - A "#" prefix opts the user out of all experiments 2025-12-04T08:51:24.8444841Z 2025-12-04T08:51:24.8445006Z Example config: 2025-12-04T08:51:24.8445426Z # A list of experiments that can be opted into. 2025-12-04T08:51:24.8446050Z # This defines the behavior they'll induce when opted into. 2025-12-04T08:51:24.8446625Z # Expected syntax is: 2025-12-04T08:51:24.8447220Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T08:51:24.8448120Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T08:51:24.8448686Z 2025-12-04T08:51:24.8448842Z experiments: 2025-12-04T08:51:24.8449205Z lf: 2025-12-04T08:51:24.8449553Z rollout_percent: 25 2025-12-04T08:51:24.8450136Z all_branches: false 2025-12-04T08:51:24.8450554Z default: true 2025-12-04T08:51:24.8450934Z --- 2025-12-04T08:51:24.8451120Z 2025-12-04T08:51:24.8451272Z # Opt-ins: 2025-12-04T08:51:24.8451824Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T08:51:24.8452632Z # and specifying experiments to enable in a comma-separated list. 2025-12-04T08:51:24.8453384Z # To always opt out of an experiment, prefix it with a "-". 2025-12-04T08:51:24.8453992Z # Experiments should be from the above list. 2025-12-04T08:51:24.8454344Z 2025-12-04T08:51:24.8455193Z @User1,-lf,split_build 2025-12-04T08:51:24.8455638Z @User2,lf 2025-12-04T08:51:24.8455997Z @User3,split_build 2025-12-04T08:51:24.8456382Z """ 2025-12-04T08:51:24.8456562Z 2025-12-04T08:51:24.8456710Z import json 2025-12-04T08:51:24.8457059Z import logging 2025-12-04T08:51:24.8457411Z import os 2025-12-04T08:51:24.8457753Z import random 2025-12-04T08:51:24.8458113Z import re 2025-12-04T08:51:24.8458454Z import sys 2025-12-04T08:51:24.8458835Z from argparse import ArgumentParser 2025-12-04T08:51:24.8459321Z from collections.abc import Iterable 2025-12-04T08:51:24.8459809Z from functools import cache 2025-12-04T08:51:24.8460244Z from logging import LogRecord 2025-12-04T08:51:24.8460710Z from typing import Any, NamedTuple 2025-12-04T08:51:24.8461208Z from urllib.request import Request, urlopen 2025-12-04T08:51:24.8461555Z 2025-12-04T08:51:24.8461705Z import yaml 2025-12-04T08:51:24.8462069Z from github import Auth, Github 2025-12-04T08:51:24.8462528Z from github.Issue import Issue 2025-12-04T08:51:24.8462815Z 2025-12-04T08:51:24.8462822Z 2025-12-04T08:51:24.8463033Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T08:51:24.8463665Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T08:51:24.8464678Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T08:51:24.8465204Z 2025-12-04T08:51:24.8465428Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T08:51:24.8466118Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T08:51:24.8466590Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T08:51:24.8467102Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T08:51:24.8467432Z 2025-12-04T08:51:24.8467627Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T08:51:24.8467933Z 2025-12-04T08:51:24.8468106Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T08:51:24.8468546Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T08:51:24.8468810Z 2025-12-04T08:51:24.8468816Z 2025-12-04T08:51:24.8468997Z class Experiment(NamedTuple): 2025-12-04T08:51:24.8469446Z rollout_perc: float = ( 2025-12-04T08:51:24.8470044Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T08:51:24.8470667Z ) 2025-12-04T08:51:24.8471015Z all_branches: bool = ( 2025-12-04T08:51:24.8471600Z False # If True, the experiment is also enabled on the exception branches 2025-12-04T08:51:24.8472229Z ) 2025-12-04T08:51:24.8472561Z default: bool = ( 2025-12-04T08:51:24.8473097Z True # If True, the experiment is enabled by default for all queries 2025-12-04T08:51:24.8473691Z ) 2025-12-04T08:51:24.8473880Z 2025-12-04T08:51:24.8474049Z # Add more fields as needed 2025-12-04T08:51:24.8474327Z 2025-12-04T08:51:24.8474333Z 2025-12-04T08:51:24.8474690Z class Settings(NamedTuple): 2025-12-04T08:51:24.8475104Z """ 2025-12-04T08:51:24.8475529Z Settings for the experiments that can be opted into. 2025-12-04T08:51:24.8476052Z """ 2025-12-04T08:51:24.8476234Z 2025-12-04T08:51:24.8476440Z experiments: dict[str, Experiment] = {} 2025-12-04T08:51:24.8476781Z 2025-12-04T08:51:24.8476787Z 2025-12-04T08:51:24.8476988Z class ColorFormatter(logging.Formatter): 2025-12-04T08:51:24.8477578Z """Color codes the log messages based on the log level""" 2025-12-04T08:51:24.8477977Z 2025-12-04T08:51:24.8478134Z COLORS = { 2025-12-04T08:51:24.8478501Z "WARNING": "\033[33m", # Yellow 2025-12-04T08:51:24.8479107Z "ERROR": "\033[31m", # Red 2025-12-04T08:51:24.8479563Z "CRITICAL": "\033[31m", # Red 2025-12-04T08:51:24.8480025Z "INFO": "\033[0m", # Reset 2025-12-04T08:51:24.8480470Z "DEBUG": "\033[0m", # Reset 2025-12-04T08:51:24.8480906Z } 2025-12-04T08:51:24.8481089Z 2025-12-04T08:51:24.8481293Z def format(self, record: LogRecord) -> str: 2025-12-04T08:51:24.8481991Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T08:51:24.8482723Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T08:51:24.8483256Z return super().format(record) 2025-12-04T08:51:24.8483568Z 2025-12-04T08:51:24.8483574Z 2025-12-04T08:51:24.8483760Z handler = logging.StreamHandler() 2025-12-04T08:51:24.8484581Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T08:51:24.8485107Z 2025-12-04T08:51:24.8485335Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T08:51:24.8485879Z log.addHandler(handler) 2025-12-04T08:51:24.8486302Z log.setLevel(logging.INFO) 2025-12-04T08:51:24.8486570Z 2025-12-04T08:51:24.8486576Z 2025-12-04T08:51:24.8486817Z def set_github_output(key: str, value: str) -> None: 2025-12-04T08:51:24.8487331Z """ 2025-12-04T08:51:24.8487799Z Defines outputs of the github action that invokes this script 2025-12-04T08:51:24.8488378Z """ 2025-12-04T08:51:24.8488718Z if not GITHUB_OUTPUT: 2025-12-04T08:51:24.8489719Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T08:51:24.8490765Z log.warning( 2025-12-04T08:51:24.8491569Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T08:51:24.8492439Z ) 2025-12-04T08:51:24.8502287Z print(f"::set-output name={key}::{value}") 2025-12-04T08:51:24.8502845Z return 2025-12-04T08:51:24.8503080Z 2025-12-04T08:51:24.8503435Z with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T08:51:24.8504001Z log.info(f"Setting output: {key}='{value}'") 2025-12-04T08:51:24.8504802Z f.write(f"{key}={value}\n") 2025-12-04T08:51:24.8505113Z 2025-12-04T08:51:24.8505120Z 2025-12-04T08:51:24.8505414Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T08:51:24.8505994Z return frozenset( 2025-12-04T08:51:24.8506569Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T08:51:24.8507199Z ) 2025-12-04T08:51:24.8507387Z 2025-12-04T08:51:24.8507394Z 2025-12-04T08:51:24.8507568Z def parse_args() -> Any: 2025-12-04T08:51:24.8508078Z parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T08:51:24.8508895Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T08:51:24.8509604Z parser.add_argument( 2025-12-04T08:51:24.8510021Z "--github-issue-repo", 2025-12-04T08:51:24.8510455Z type=str, 2025-12-04T08:51:24.8510827Z required=False, 2025-12-04T08:51:24.8511246Z default="pytorch/test-infra", 2025-12-04T08:51:24.8511775Z help="GitHub repo to get the issue", 2025-12-04T08:51:24.8512241Z ) 2025-12-04T08:51:24.8512579Z parser.add_argument( 2025-12-04T08:51:24.8512982Z "--github-repo", 2025-12-04T08:51:24.8513365Z type=str, 2025-12-04T08:51:24.8513727Z required=True, 2025-12-04T08:51:24.8514147Z help="GitHub repo where CI is running", 2025-12-04T08:51:24.8514830Z ) 2025-12-04T08:51:24.8515176Z parser.add_argument( 2025-12-04T08:51:24.8515727Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T08:51:24.8516334Z ) 2025-12-04T08:51:24.8516669Z parser.add_argument( 2025-12-04T08:51:24.8517248Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T08:51:24.8517859Z ) 2025-12-04T08:51:24.8518194Z parser.add_argument( 2025-12-04T08:51:24.8518929Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T08:51:24.8519572Z ) 2025-12-04T08:51:24.8519912Z parser.add_argument( 2025-12-04T08:51:24.8520523Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T08:51:24.8521180Z ) 2025-12-04T08:51:24.8521515Z parser.add_argument( 2025-12-04T08:51:24.8521933Z "--github-ref-type", 2025-12-04T08:51:24.8522346Z type=str, 2025-12-04T08:51:24.8522715Z required=True, 2025-12-04T08:51:24.8523157Z help="Current GitHub ref type, branch or tag", 2025-12-04T08:51:24.8523661Z ) 2025-12-04T08:51:24.8523995Z parser.add_argument( 2025-12-04T08:51:24.8524599Z "--eligible-experiments", 2025-12-04T08:51:24.8525079Z type=_str_comma_separated_to_set, 2025-12-04T08:51:24.8525558Z required=False, 2025-12-04T08:51:24.8525940Z default="", 2025-12-04T08:51:24.8526727Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T08:51:24.8527596Z ) 2025-12-04T08:51:24.8578388Z parser.add_argument( 2025-12-04T08:51:24.8579007Z "--opt-out-experiments", 2025-12-04T08:51:24.8579540Z type=_str_comma_separated_to_set, 2025-12-04T08:51:24.8580055Z required=False, 2025-12-04T08:51:24.8580452Z default="", 2025-12-04T08:51:24.8580820Z help=( 2025-12-04T08:51:24.8581450Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T08:51:24.8582524Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T08:51:24.8583307Z ), 2025-12-04T08:51:24.8583634Z ) 2025-12-04T08:51:24.8583981Z parser.add_argument( 2025-12-04T08:51:24.8584610Z "--pr-number", 2025-12-04T08:51:24.8585024Z type=str, 2025-12-04T08:51:24.8585388Z required=False, 2025-12-04T08:51:24.8585800Z default="", 2025-12-04T08:51:24.8586419Z help="the optional PR number where this is run", 2025-12-04T08:51:24.8586955Z ) 2025-12-04T08:51:24.8587143Z 2025-12-04T08:51:24.8587322Z return parser.parse_args() 2025-12-04T08:51:24.8587616Z 2025-12-04T08:51:24.8587622Z 2025-12-04T08:51:24.8587997Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T08:51:24.8588698Z auth = Auth.Token(github_token) 2025-12-04T08:51:24.8589168Z return Github(auth=auth) 2025-12-04T08:51:24.8589449Z 2025-12-04T08:51:24.8589456Z 2025-12-04T08:51:24.8589875Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T08:51:24.8590618Z repo = gh.get_repo(repo) 2025-12-04T08:51:24.8591085Z return repo.get_issue(number=issue_num) 2025-12-04T08:51:24.8591419Z 2025-12-04T08:51:24.8591426Z 2025-12-04T08:51:24.8591605Z def get_potential_pr_author( 2025-12-04T08:51:24.8592205Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T08:51:24.8592840Z ) -> str: 2025-12-04T08:51:24.8593316Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T08:51:24.8594062Z # Fetch the actual username from the original PR. The PR number is 2025-12-04T08:51:24.8594976Z # embedded in the tag name: ciflow// 2025-12-04T08:51:24.8595377Z 2025-12-04T08:51:24.8595554Z gh = get_gh_client(github_token) 2025-12-04T08:51:24.8595861Z 2025-12-04T08:51:24.8596114Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T08:51:24.8596688Z split_tag = ref_name.split("/") 2025-12-04T08:51:24.8597157Z if ( 2025-12-04T08:51:24.8597514Z len(split_tag) == 3 2025-12-04T08:51:24.8597966Z and split_tag[0] == "ciflow" 2025-12-04T08:51:24.8598457Z and split_tag[2].isnumeric() 2025-12-04T08:51:24.8598922Z ): 2025-12-04T08:51:24.8599276Z pr_number = split_tag[2] 2025-12-04T08:51:24.8599877Z try: 2025-12-04T08:51:24.8600279Z repository = gh.get_repo(repo) 2025-12-04T08:51:24.8600845Z pull = repository.get_pull(number=int(pr_number)) 2025-12-04T08:51:24.8601405Z except Exception as e: 2025-12-04T08:51:24.8601882Z raise Exception( # noqa: TRY002 2025-12-04T08:51:24.8602508Z f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T08:51:24.8603104Z ) from e 2025-12-04T08:51:24.8603603Z return pull.user.login # type: ignore[no-any-return] 2025-12-04T08:51:24.8604247Z # In all other cases, return the original input username 2025-12-04T08:51:24.8605001Z return username 2025-12-04T08:51:24.8605226Z 2025-12-04T08:51:24.8605232Z 2025-12-04T08:51:24.8605451Z def is_exception_branch(branch: str) -> bool: 2025-12-04T08:51:24.8605939Z """ 2025-12-04T08:51:24.8606532Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T08:51:24.8607250Z """ 2025-12-04T08:51:24.8607756Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T08:51:24.8608244Z 2025-12-04T08:51:24.8608251Z 2025-12-04T08:51:24.8608445Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T08:51:24.8608897Z try: 2025-12-04T08:51:24.8609254Z data = yaml.safe_load(yaml_text) 2025-12-04T08:51:24.8609724Z return data 2025-12-04T08:51:24.8610113Z except yaml.YAMLError: 2025-12-04T08:51:24.8610552Z log.exception("Error loading YAML") 2025-12-04T08:51:24.8611025Z raise 2025-12-04T08:51:24.8611222Z 2025-12-04T08:51:24.8611228Z 2025-12-04T08:51:24.8611637Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T08:51:24.8612324Z """ 2025-12-04T08:51:24.8612905Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T08:51:24.8613454Z 2025-12-04T08:51:24.8613904Z If the issue body contains "---" then the text above that is the settings 2025-12-04T08:51:24.8614839Z and the text below is the list of opted in users. 2025-12-04T08:51:24.8615218Z 2025-12-04T08:51:24.8615562Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T08:51:24.8616215Z """ 2025-12-04T08:51:24.8616620Z rollout_state_parts = rollout_state.split("---") 2025-12-04T08:51:24.8617176Z if len(rollout_state_parts) >= 2: 2025-12-04T08:51:24.8617736Z return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T08:51:24.8618275Z else: 2025-12-04T08:51:24.8618627Z return "", rollout_state 2025-12-04T08:51:24.8618914Z 2025-12-04T08:51:24.8618921Z 2025-12-04T08:51:24.8619105Z class UserOptins(dict[str, list[str]]): 2025-12-04T08:51:24.8619576Z """ 2025-12-04T08:51:24.8620052Z Dictionary of users with a list of features they have opted into 2025-12-04T08:51:24.8620650Z """ 2025-12-04T08:51:24.8620835Z 2025-12-04T08:51:24.8620841Z 2025-12-04T08:51:24.8621160Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T08:51:24.8621758Z """ 2025-12-04T08:51:24.8622410Z 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:51:24.8623037Z 2025-12-04T08:51:24.8623608Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T08:51:24.8624714Z - Example line: "@User1,lf,split_build" 2025-12-04T08:51:24.8625345Z - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T08:51:24.8625792Z 2025-12-04T08:51:24.8625798Z 2025-12-04T08:51:24.8625941Z """ 2025-12-04T08:51:24.8626291Z optins = UserOptins() 2025-12-04T08:51:24.8626738Z for user in user_optin_text.split("\n"): 2025-12-04T08:51:24.8627251Z user = user.strip("\r\n\t -") 2025-12-04T08:51:24.8627755Z if not user or not user.startswith("@"): 2025-12-04T08:51:24.8628417Z # Not a valid user. Skip 2025-12-04T08:51:24.8628866Z continue 2025-12-04T08:51:24.8629101Z 2025-12-04T08:51:24.8629250Z if user: 2025-12-04T08:51:24.8629655Z usr_name = user.split(",")[0].strip("@") 2025-12-04T08:51:24.8630297Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T08:51:24.8630752Z 2025-12-04T08:51:24.8630919Z return optins 2025-12-04T08:51:24.8631137Z 2025-12-04T08:51:24.8631144Z 2025-12-04T08:51:24.8631407Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T08:51:24.8631967Z """ 2025-12-04T08:51:24.8632326Z Check if the experiment name is valid. 2025-12-04T08:51:24.8632819Z A valid name: 2025-12-04T08:51:24.8633406Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T08:51:24.8634267Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T08:51:24.8635124Z - Cannot contain spaces 2025-12-04T08:51:24.8635550Z """ 2025-12-04T08:51:24.8635741Z 2025-12-04T08:51:24.8635983Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T08:51:24.8636621Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T08:51:24.8637038Z 2025-12-04T08:51:24.8637189Z if valid: 2025-12-04T08:51:24.8637532Z return True 2025-12-04T08:51:24.8637756Z 2025-12-04T08:51:24.8637905Z log.error( 2025-12-04T08:51:24.8639248Z 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:51:24.8640727Z ) 2025-12-04T08:51:24.8641056Z return False 2025-12-04T08:51:24.8641272Z 2025-12-04T08:51:24.8641278Z 2025-12-04T08:51:24.8641564Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T08:51:24.8642135Z """ 2025-12-04T08:51:24.8642811Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T08:51:24.8643474Z """ 2025-12-04T08:51:24.8643802Z try: 2025-12-04T08:51:24.8644139Z if settings_text: 2025-12-04T08:51:24.8645050Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T08:51:24.8645790Z # for easy reading 2025-12-04T08:51:24.8646514Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T08:51:24.8647342Z # the backtick character in shell commands. 2025-12-04T08:51:24.8647891Z backtick = chr(96) # backtick character 2025-12-04T08:51:24.8648508Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T08:51:24.8649120Z settings = load_yaml(settings_text) 2025-12-04T08:51:24.8649469Z 2025-12-04T08:51:24.8649851Z # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T08:51:24.8650555Z experiments = {} 2025-12-04T08:51:24.8650827Z 2025-12-04T08:51:24.8651178Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T08:51:24.8651884Z if not is_valid_experiment_name(exp_name): 2025-12-04T08:51:24.8652901Z # 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:51:24.8653864Z continue 2025-12-04T08:51:24.8654126Z 2025-12-04T08:51:24.8654301Z valid_settings = {} 2025-12-04T08:51:24.8655008Z for setting in exp_settings: 2025-12-04T08:51:24.8655540Z if setting not in Experiment._fields: 2025-12-04T08:51:24.8656047Z log.warning( 2025-12-04T08:51:24.8656692Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T08:51:24.8657501Z ) 2025-12-04T08:51:24.8657898Z else: 2025-12-04T08:51:24.8658369Z valid_settings[setting] = exp_settings[setting] 2025-12-04T08:51:24.8658762Z 2025-12-04T08:51:24.8659010Z experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T08:51:24.8659590Z return Settings(experiments) 2025-12-04T08:51:24.8659914Z 2025-12-04T08:51:24.8660074Z except Exception: 2025-12-04T08:51:24.8660517Z log.exception("Failed to parse settings") 2025-12-04T08:51:24.8660876Z 2025-12-04T08:51:24.8661030Z return Settings() 2025-12-04T08:51:24.8661272Z 2025-12-04T08:51:24.8661278Z 2025-12-04T08:51:24.8661508Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T08:51:24.8662026Z """ 2025-12-04T08:51:24.8662426Z Parse settings, if any, from the rollout state. 2025-12-04T08:51:24.8662796Z 2025-12-04T08:51:24.8663119Z If the issue body contains "---" then the text above that is the settings 2025-12-04T08:51:24.8663827Z and the text below is the list of opted in users. 2025-12-04T08:51:24.8664207Z 2025-12-04T08:51:24.8664686Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T08:51:24.8665361Z """ 2025-12-04T08:51:24.8665875Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:51:24.8666568Z return parse_settings_from_text(settings_text) 2025-12-04T08:51:24.8666934Z 2025-12-04T08:51:24.8666940Z 2025-12-04T08:51:24.8667163Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T08:51:24.8667676Z """ 2025-12-04T08:51:24.8668028Z Parse users from the rollout state. 2025-12-04T08:51:24.8668355Z 2025-12-04T08:51:24.8668502Z """ 2025-12-04T08:51:24.8668977Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:51:24.8669648Z return parse_user_opt_in_from_text(users_text) 2025-12-04T08:51:24.8670013Z 2025-12-04T08:51:24.8670019Z 2025-12-04T08:51:24.8670524Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:51:24.8671203Z """ 2025-12-04T08:51:24.8671580Z Check if a user is opted into an experiment 2025-12-04T08:51:24.8672067Z """ 2025-12-04T08:51:24.8672479Z return experiment_name in user_optins.get(user, []) 2025-12-04T08:51:24.8672859Z 2025-12-04T08:51:24.8672865Z 2025-12-04T08:51:24.8673245Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:51:24.8673924Z """ 2025-12-04T08:51:24.8674338Z Check if a user explicitly opted out of an experiment 2025-12-04T08:51:24.8675023Z """ 2025-12-04T08:51:24.8675486Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T08:51:24.8676113Z experiment_optout = "-" + experiment_name 2025-12-04T08:51:24.8676696Z if experiment_optout not in user_optins.get(user, []): 2025-12-04T08:51:24.8677241Z return False 2025-12-04T08:51:24.8677482Z 2025-12-04T08:51:24.8677734Z if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T08:51:24.8678279Z log.warning( 2025-12-04T08:51:24.8679015Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T08:51:24.8679822Z ) 2025-12-04T08:51:24.8680011Z 2025-12-04T08:51:24.8680160Z return True 2025-12-04T08:51:24.8680372Z 2025-12-04T08:51:24.8680384Z 2025-12-04T08:51:24.8680548Z def get_runner_prefix( 2025-12-04T08:51:24.8680943Z rollout_state: str, 2025-12-04T08:51:24.8681370Z workflow_requestors: Iterable[str], 2025-12-04T08:51:24.8681836Z branch: str, 2025-12-04T08:51:24.8682287Z eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T08:51:24.8682901Z opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T08:51:24.8683432Z is_canary: bool = False, 2025-12-04T08:51:24.8683843Z ) -> str: 2025-12-04T08:51:24.8684350Z settings = parse_settings(rollout_state) 2025-12-04T08:51:24.8685349Z user_optins = parse_users(rollout_state) 2025-12-04T08:51:24.8685691Z 2025-12-04T08:51:24.8685852Z fleet_prefix = "" 2025-12-04T08:51:24.8686238Z prefixes = [] 2025-12-04T08:51:24.8686807Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T08:51:24.8687662Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T08:51:24.8688309Z log.info( 2025-12-04T08:51:24.8688921Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T08:51:24.8689619Z ) 2025-12-04T08:51:24.8689957Z continue 2025-12-04T08:51:24.8690193Z 2025-12-04T08:51:24.8690365Z if opt_out_experiments: 2025-12-04T08:51:24.8690848Z if experiment_name in opt_out_experiments: 2025-12-04T08:51:24.8691440Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T08:51:24.8691984Z log.info( 2025-12-04T08:51:24.8692841Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T08:51:24.8693744Z ) 2025-12-04T08:51:24.8694099Z continue 2025-12-04T08:51:24.8694345Z 2025-12-04T08:51:24.8694619Z if eligible_experiments: 2025-12-04T08:51:24.8695148Z if experiment_name not in eligible_experiments: 2025-12-04T08:51:24.8695733Z exp_list = ", ".join(eligible_experiments) 2025-12-04T08:51:24.8696250Z log.info( 2025-12-04T08:51:24.8696960Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T08:51:24.8697730Z ) 2025-12-04T08:51:24.8698088Z continue 2025-12-04T08:51:24.8698526Z elif not experiment_settings.default: 2025-12-04T08:51:24.8699005Z log.info( 2025-12-04T08:51:24.8699780Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T08:51:24.8700454Z ) 2025-12-04T08:51:24.8700797Z continue 2025-12-04T08:51:24.8701019Z 2025-12-04T08:51:24.8701274Z # Is any workflow_requestor opted out to this experiment? 2025-12-04T08:51:24.8701835Z opted_out_users = [ 2025-12-04T08:51:24.8702242Z requestor 2025-12-04T08:51:24.8702654Z for requestor in workflow_requestors 2025-12-04T08:51:24.8703266Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T08:51:24.8703834Z ] 2025-12-04T08:51:24.8704027Z 2025-12-04T08:51:24.8704190Z if opted_out_users: 2025-12-04T08:51:24.8704692Z log.info( 2025-12-04T08:51:24.8705249Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T08:51:24.8705881Z ) 2025-12-04T08:51:24.8706221Z continue 2025-12-04T08:51:24.8706447Z 2025-12-04T08:51:24.8706696Z # Is any workflow_requestor opted in to this experiment? 2025-12-04T08:51:24.8707245Z opted_in_users = [ 2025-12-04T08:51:24.8707648Z requestor 2025-12-04T08:51:24.8708056Z for requestor in workflow_requestors 2025-12-04T08:51:24.8708658Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T08:51:24.8709217Z ] 2025-12-04T08:51:24.8709407Z 2025-12-04T08:51:24.8709563Z enabled = False 2025-12-04T08:51:24.8709955Z if opted_in_users: 2025-12-04T08:51:24.8710352Z log.info( 2025-12-04T08:51:24.8710895Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T08:51:24.8711511Z ) 2025-12-04T08:51:24.8711898Z enabled = True 2025-12-04T08:51:24.8712151Z 2025-12-04T08:51:24.8712347Z elif experiment_settings.rollout_perc: 2025-12-04T08:51:24.8713115Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T08:51:24.8714093Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T08:51:24.8714773Z log.info( 2025-12-04T08:51:24.8715560Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T08:51:24.8716405Z ) 2025-12-04T08:51:24.8716783Z enabled = True 2025-12-04T08:51:24.8717052Z 2025-12-04T08:51:24.8717203Z if enabled: 2025-12-04T08:51:24.8717585Z label = experiment_name 2025-12-04T08:51:24.8718086Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T08:51:24.8718834Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T08:51:24.8719638Z # - If it's enabled, then we always list it's prefix first 2025-12-04T08:51:24.8720319Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T08:51:24.8720931Z if is_canary: 2025-12-04T08:51:24.8721374Z label += CANARY_FLEET_SUFFIX 2025-12-04T08:51:24.8721878Z fleet_prefix = label 2025-12-04T08:51:24.8722321Z else: 2025-12-04T08:51:24.8722726Z prefixes.append(label) 2025-12-04T08:51:24.8723039Z 2025-12-04T08:51:24.8723211Z if len(prefixes) > 1: 2025-12-04T08:51:24.8723607Z log.error( 2025-12-04T08:51:24.8724657Z 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:51:24.8725674Z ) 2025-12-04T08:51:24.8726028Z prefixes = prefixes[:1] 2025-12-04T08:51:24.8726306Z 2025-12-04T08:51:24.8726473Z # Fleet always comes first 2025-12-04T08:51:24.8726898Z if fleet_prefix: 2025-12-04T08:51:24.8727308Z prefixes.insert(0, fleet_prefix) 2025-12-04T08:51:24.8727638Z 2025-12-04T08:51:24.8727977Z return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T08:51:24.8728359Z 2025-12-04T08:51:24.8728366Z 2025-12-04T08:51:24.8728770Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T08:51:24.8729464Z """ 2025-12-04T08:51:24.8729999Z Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T08:51:24.8730509Z 2025-12-04T08:51:24.8730867Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T08:51:24.8731503Z """ 2025-12-04T08:51:24.8731853Z gh = get_gh_client(github_token) 2025-12-04T08:51:24.8732342Z issue = get_issue(gh, repo, issue_num) 2025-12-04T08:51:24.8732924Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T08:51:24.8733323Z 2025-12-04T08:51:24.8733329Z 2025-12-04T08:51:24.8733688Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T08:51:24.8734474Z for _ in range(num_retries): 2025-12-04T08:51:24.8734902Z try: 2025-12-04T08:51:24.8735285Z req = Request(url=url, headers=headers) 2025-12-04T08:51:24.8735891Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T08:51:24.8736469Z return json.loads(content) 2025-12-04T08:51:24.8736945Z except Exception as e: 2025-12-04T08:51:24.8737430Z log.warning(f"Could not download {url}: {e}") 2025-12-04T08:51:24.8737798Z 2025-12-04T08:51:24.8738145Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T08:51:24.8738792Z return {} 2025-12-04T08:51:24.8739001Z 2025-12-04T08:51:24.8739007Z 2025-12-04T08:51:24.8739149Z @cache 2025-12-04T08:51:24.8739714Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T08:51:24.8740402Z """ 2025-12-04T08:51:24.8740756Z Dynamically get PR information 2025-12-04T08:51:24.8741322Z """ 2025-12-04T08:51:24.8741769Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T08:51:24.8742329Z headers = { 2025-12-04T08:51:24.8742747Z "Accept": "application/vnd.github.v3+json", 2025-12-04T08:51:24.8743303Z "Authorization": f"token {github_token}", 2025-12-04T08:51:24.8743791Z } 2025-12-04T08:51:24.8744178Z json_response: dict[str, Any] = download_json( 2025-12-04T08:51:24.8744818Z url=f"{github_api}/issues/{pr_number}", 2025-12-04T08:51:24.8745316Z headers=headers, 2025-12-04T08:51:24.8745699Z ) 2025-12-04T08:51:24.8745879Z 2025-12-04T08:51:24.8746054Z if not json_response: 2025-12-04T08:51:24.8746562Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T08:51:24.8747123Z return {} 2025-12-04T08:51:24.8747338Z 2025-12-04T08:51:24.8747499Z return json_response 2025-12-04T08:51:24.8747750Z 2025-12-04T08:51:24.8747757Z 2025-12-04T08:51:24.8748122Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T08:51:24.8748791Z """ 2025-12-04T08:51:24.8749261Z Dynamically get the latest list of labels from the pull request 2025-12-04T08:51:24.8749860Z """ 2025-12-04T08:51:24.8750289Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T08:51:24.8750860Z return { 2025-12-04T08:51:24.8751391Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T08:51:24.8752032Z } 2025-12-04T08:51:24.8752213Z 2025-12-04T08:51:24.8752219Z 2025-12-04T08:51:24.8752384Z def main() -> None: 2025-12-04T08:51:24.8752764Z args = parse_args() 2025-12-04T08:51:24.8753011Z 2025-12-04T08:51:24.8753218Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T08:51:24.8753567Z 2025-12-04T08:51:24.8753740Z # Check if the PR is opt-out 2025-12-04T08:51:24.8754184Z if args.pr_number: 2025-12-04T08:51:24.8754873Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T08:51:24.8755681Z if OPT_OUT_LABEL in labels: 2025-12-04T08:51:24.8756138Z log.info( 2025-12-04T08:51:24.8756760Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T08:51:24.8757455Z ) 2025-12-04T08:51:24.8757947Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:51:24.8758550Z sys.exit() 2025-12-04T08:51:24.8758785Z 2025-12-04T08:51:24.8758928Z try: 2025-12-04T08:51:24.8759323Z rollout_state = get_rollout_state_from_issue( 2025-12-04T08:51:24.8759961Z args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T08:51:24.8760544Z ) 2025-12-04T08:51:24.8760729Z 2025-12-04T08:51:24.8760917Z username = get_potential_pr_author( 2025-12-04T08:51:24.8761406Z args.github_token, 2025-12-04T08:51:24.8761844Z args.github_repo, 2025-12-04T08:51:24.8762271Z args.github_actor, 2025-12-04T08:51:24.8762714Z args.github_ref_type, 2025-12-04T08:51:24.8763162Z args.github_branch, 2025-12-04T08:51:24.8763579Z ) 2025-12-04T08:51:24.8763764Z 2025-12-04T08:51:24.8764016Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T08:51:24.8764527Z 2025-12-04T08:51:24.8764724Z runner_label_prefix = get_runner_prefix( 2025-12-04T08:51:24.8765231Z rollout_state, 2025-12-04T08:51:24.8765669Z (args.github_issue_owner, username), 2025-12-04T08:51:24.8766166Z args.github_branch, 2025-12-04T08:51:24.8766610Z args.eligible_experiments, 2025-12-04T08:51:24.8767102Z args.opt_out_experiments, 2025-12-04T08:51:24.8767556Z is_canary, 2025-12-04T08:51:24.8767932Z ) 2025-12-04T08:51:24.8768119Z 2025-12-04T08:51:24.8768283Z except Exception as e: 2025-12-04T08:51:24.8768692Z log.error( 2025-12-04T08:51:24.8769300Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T08:51:24.8770120Z ) 2025-12-04T08:51:24.8770311Z 2025-12-04T08:51:24.8770611Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:51:24.8771057Z 2025-12-04T08:51:24.8771063Z 2025-12-04T08:51:24.8771221Z if __name__ == "__main__": 2025-12-04T08:51:24.8771621Z main() 2025-12-04T08:51:24.8771806Z 2025-12-04T08:51:24.8857713Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T08:51:24.8858532Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T08:51:24.8890633Z shell: /usr/bin/bash -e {0} 2025-12-04T08:51:24.8891065Z env: 2025-12-04T08:51:24.8891614Z GITHUB_TOKEN: *** 2025-12-04T08:51:24.8891990Z ISSUE_NUMBER: 5132 2025-12-04T08:51:24.8892399Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:51:24.8892847Z ISSUE_OWNER: 2025-12-04T08:51:24.8893207Z CHECK_EXPERIMENTS: 2025-12-04T08:51:24.8893601Z OPT_OUT_EXPERIMENTS: lf 2025-12-04T08:51:24.8894011Z PR_NUMBER: 2025-12-04T08:51:24.8894348Z ##[endgroup] 2025-12-04T08:51:26.1220769Z Defaulting to user installation because normal site-packages is not writeable 2025-12-04T08:51:26.9594638Z Collecting urllib3==1.26.18 2025-12-04T08:51:27.0558424Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-12-04T08:51:27.0854068Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 2.1 MB/s eta 0:00:00 2025-12-04T08:51:27.1255534Z Collecting PyGithub==2.3.0 2025-12-04T08:51:27.1442132Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-12-04T08:51:27.2106177Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-12-04T08:51:27.2291791Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.8 kB) 2025-12-04T08:51:27.2338202Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-12-04T08:51:27.2355551Z 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:51:27.2370031Z 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:51:27.2867007Z Collecting Deprecated (from PyGithub==2.3.0) 2025-12-04T08:51:27.3052580Z Downloading deprecated-1.3.1-py2.py3-none-any.whl.metadata (5.9 kB) 2025-12-04T08:51:27.3280989Z 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:51:27.4901825Z Collecting cffi>=2.0.0 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T08:51:27.5090049Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-12-04T08:51:27.6860913Z Collecting wrapt<3,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-12-04T08:51:27.7048540Z 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:51:27.7408201Z Collecting pycparser (from cffi>=2.0.0->pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T08:51:27.7594018Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-12-04T08:51:27.7996018Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-12-04T08:51:27.8226792Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 6.5 MB/s eta 0:00:00 2025-12-04T08:51:27.8412740Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-12-04T08:51:27.8637440Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 16.6 MB/s eta 0:00:00 2025-12-04T08:51:27.8826103Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-12-04T08:51:27.9119865Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 50.9 MB/s eta 0:00:00 2025-12-04T08:51:27.9309805Z Downloading deprecated-1.3.1-py2.py3-none-any.whl (11 kB) 2025-12-04T08:51:27.9519718Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-12-04T08:51:27.9577411Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 59.1 MB/s eta 0:00:00 2025-12-04T08:51:27.9766383Z 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:51:27.9817334Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.5/121.5 kB 35.4 MB/s eta 0:00:00 2025-12-04T08:51:27.9999137Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-12-04T08:51:28.0042155Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 42.0 MB/s eta 0:00:00 2025-12-04T08:51:28.2978714Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-12-04T08:51:28.8381723Z 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:51:28.9207072Z ##[group]Run curr_branch="main" 2025-12-04T08:51:28.9207388Z curr_branch="main" 2025-12-04T08:51:28.9207610Z curr_ref_type="branch" 2025-12-04T08:51:28.9207872Z echo "Current branch is '$curr_branch'" 2025-12-04T08:51:28.9208165Z  2025-12-04T08:51:28.9208367Z python3 runner_determinator.py \ 2025-12-04T08:51:28.9208646Z  --github-token "$GITHUB_TOKEN" \ 2025-12-04T08:51:28.9208932Z  --github-issue "$ISSUE_NUMBER" \ 2025-12-04T08:51:28.9209199Z  --github-branch "$curr_branch" \ 2025-12-04T08:51:28.9209464Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-12-04T08:51:28.9209749Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-12-04T08:51:28.9210037Z  --github-ref-type "$curr_ref_type" \ 2025-12-04T08:51:28.9210318Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-12-04T08:51:28.9210611Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-12-04T08:51:28.9210999Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-12-04T08:51:28.9211292Z  --pr-number "${PR_NUMBER}" 2025-12-04T08:51:28.9245487Z shell: /usr/bin/bash -e {0} 2025-12-04T08:51:28.9245725Z env: 2025-12-04T08:51:28.9246295Z GITHUB_TOKEN: *** 2025-12-04T08:51:28.9246495Z ISSUE_NUMBER: 5132 2025-12-04T08:51:28.9246701Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:51:28.9246930Z ISSUE_OWNER: 2025-12-04T08:51:28.9247118Z CHECK_EXPERIMENTS: 2025-12-04T08:51:28.9247310Z OPT_OUT_EXPERIMENTS: lf 2025-12-04T08:51:28.9247504Z PR_NUMBER: 2025-12-04T08:51:28.9247697Z ##[endgroup] 2025-12-04T08:51:28.9297734Z Current branch is 'main' 2025-12-04T08:51:30.6572328Z INFO : Skipping experiment 'lf', as this workflow has opted-out (opted out experiments are: lf) 2025-12-04T08:51:30.6574949Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-12-04T08:51:30.6575862Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-12-04T08:51:30.6576734Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-12-04T08:51:30.6577431Z INFO : Setting output: label-type='' 2025-12-04T08:51:30.6895617Z Evaluate and set job outputs 2025-12-04T08:51:30.6902582Z Cleaning up orphan processes