2025-12-04T07:56:47.8374807Z Current runner version: '2.329.0' 2025-12-04T07:56:47.8399419Z ##[group]Runner Image Provisioner 2025-12-04T07:56:47.8400762Z Hosted Compute Agent 2025-12-04T07:56:47.8401269Z Version: 20251124.448 2025-12-04T07:56:47.8401962Z Commit: fda5086b43ec66ade217e5fcd18146c879571177 2025-12-04T07:56:47.8402652Z Build Date: 2025-11-24T21:16:26Z 2025-12-04T07:56:47.8403262Z ##[endgroup] 2025-12-04T07:56:47.8403873Z ##[group]Operating System 2025-12-04T07:56:47.8404434Z Ubuntu 2025-12-04T07:56:47.8404904Z 24.04.3 2025-12-04T07:56:47.8405372Z LTS 2025-12-04T07:56:47.8405842Z ##[endgroup] 2025-12-04T07:56:47.8406299Z ##[group]Runner Image 2025-12-04T07:56:47.8406904Z Image: ubuntu-24.04 2025-12-04T07:56:47.8407431Z Version: 20251126.144.1 2025-12-04T07:56:47.8408834Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251126.144/images/ubuntu/Ubuntu2404-Readme.md 2025-12-04T07:56:47.8410542Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251126.144 2025-12-04T07:56:47.8411558Z ##[endgroup] 2025-12-04T07:56:47.8412753Z ##[group]GITHUB_TOKEN Permissions 2025-12-04T07:56:47.8414954Z Contents: read 2025-12-04T07:56:47.8415542Z Metadata: read 2025-12-04T07:56:47.8416091Z Packages: read 2025-12-04T07:56:47.8416558Z ##[endgroup] 2025-12-04T07:56:47.8419651Z Secret source: Actions 2025-12-04T07:56:47.8420699Z Prepare workflow directory 2025-12-04T07:56:47.8925690Z Prepare all required actions 2025-12-04T07:56:47.8983986Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (ffd9b0fb4355e97af82fc42cf185c3ffa0fc0a32) 2025-12-04T07:56:47.8989217Z ##[group] Inputs 2025-12-04T07:56:47.8989812Z check_experiments: 2025-12-04T07:56:47.8990399Z opt_out_experiments: 2025-12-04T07:56:47.8990979Z triggering_actor: pytorchmergebot 2025-12-04T07:56:47.8991642Z issue_owner: 2025-12-04T07:56:47.8992108Z curr_branch: main 2025-12-04T07:56:47.8992774Z curr_ref_type: branch 2025-12-04T07:56:47.8993336Z issue_number: 5132 2025-12-04T07:56:47.8993842Z ##[endgroup] 2025-12-04T07:56:47.8994479Z Complete job name: get-label-type / runner-determinator 2025-12-04T07:56:47.9580275Z ##[group]Run cat < runner_determinator.py 2025-12-04T07:56:47.9582563Z cat < runner_determinator.py 2025-12-04T07:56:47.9583290Z # flake8: noqa: G004 2025-12-04T07:56:47.9583827Z  2025-12-04T07:56:47.9584507Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T07:56:47.9585630Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T07:56:47.9586556Z # python .github/scripts/update_runner_determinator.py 2025-12-04T07:56:47.9587242Z  2025-12-04T07:56:47.9587708Z """ 2025-12-04T07:56:47.9588603Z This runner determinator is used to determine which set of runners to run a 2025-12-04T07:56:47.9589606Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T07:56:47.9590847Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T07:56:47.9591773Z of which runners should be used to run which job. 2025-12-04T07:56:47.9592499Z  2025-12-04T07:56:47.9593150Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T07:56:47.9594175Z separated by a line containing "---". If the line is not present, the 2025-12-04T07:56:47.9595224Z settings are considered to be empty with only the second part, the user 2025-12-04T07:56:47.9596032Z list, defined. 2025-12-04T07:56:47.9596534Z  2025-12-04T07:56:47.9597182Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T07:56:47.9598373Z used to define any settings that are needed to determine which runners to use. 2025-12-04T07:56:47.9599349Z It's fields are defined by the RolloutSettings class below. 2025-12-04T07:56:47.9600381Z  2025-12-04T07:56:47.9601091Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T07:56:47.9602039Z The user list is also a comma separated list of additional features or 2025-12-04T07:56:47.9602959Z experiments which the user could be opted in to. 2025-12-04T07:56:47.9603625Z  2025-12-04T07:56:47.9604078Z The user list has the following rules: 2025-12-04T07:56:47.9604719Z  2025-12-04T07:56:47.9605326Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T07:56:47.9606278Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T07:56:47.9607199Z - A "#" prefix opts the user out of all experiments 2025-12-04T07:56:47.9608095Z  2025-12-04T07:56:47.9608634Z Example config: 2025-12-04T07:56:47.9609296Z  # A list of experiments that can be opted into. 2025-12-04T07:56:47.9610132Z  # This defines the behavior they'll induce when opted into. 2025-12-04T07:56:47.9610816Z  # Expected syntax is: 2025-12-04T07:56:47.9611668Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T07:56:47.9612744Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T07:56:47.9613544Z  2025-12-04T07:56:47.9614035Z  experiments: 2025-12-04T07:56:47.9614533Z  lf: 2025-12-04T07:56:47.9615019Z  rollout_percent: 25 2025-12-04T07:56:47.9615600Z  all_branches: false 2025-12-04T07:56:47.9616192Z  default: true 2025-12-04T07:56:47.9616719Z  --- 2025-12-04T07:56:47.9617177Z  2025-12-04T07:56:47.9617671Z  # Opt-ins: 2025-12-04T07:56:47.9618605Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T07:56:47.9619811Z  # and specifying experiments to enable in a comma-separated list. 2025-12-04T07:56:47.9620693Z  # To always opt out of an experiment, prefix it with a "-". 2025-12-04T07:56:47.9621464Z  # Experiments should be from the above list. 2025-12-04T07:56:47.9622184Z  2025-12-04T07:56:47.9622607Z  @User1,-lf,split_build 2025-12-04T07:56:47.9623170Z  @User2,lf 2025-12-04T07:56:47.9623691Z  @User3,split_build 2025-12-04T07:56:47.9624260Z """ 2025-12-04T07:56:47.9624680Z  2025-12-04T07:56:47.9625161Z import json 2025-12-04T07:56:47.9625721Z import logging 2025-12-04T07:56:47.9626266Z import os 2025-12-04T07:56:47.9626799Z import random 2025-12-04T07:56:47.9627294Z import re 2025-12-04T07:56:47.9627930Z import sys 2025-12-04T07:56:47.9628568Z from argparse import ArgumentParser 2025-12-04T07:56:47.9629206Z from collections.abc import Iterable 2025-12-04T07:56:47.9629845Z from functools import cache 2025-12-04T07:56:47.9630456Z from logging import LogRecord 2025-12-04T07:56:47.9631111Z from typing import Any, NamedTuple 2025-12-04T07:56:47.9631759Z from urllib.request import Request, urlopen 2025-12-04T07:56:47.9632422Z  2025-12-04T07:56:47.9632909Z import yaml 2025-12-04T07:56:47.9633402Z from github import Auth, Github 2025-12-04T07:56:47.9634056Z from github.Issue import Issue 2025-12-04T07:56:47.9634620Z  2025-12-04T07:56:47.9635051Z  2025-12-04T07:56:47.9635525Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T07:56:47.9636448Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T07:56:47.9637442Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T07:56:47.9638523Z  2025-12-04T07:56:47.9639143Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T07:56:47.9639801Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T07:56:47.9640541Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T07:56:47.9641243Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T07:56:47.9641827Z  2025-12-04T07:56:47.9642346Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T07:56:47.9642926Z  2025-12-04T07:56:47.9643393Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T07:56:47.9643959Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T07:56:47.9644854Z  2025-12-04T07:56:47.9645251Z  2025-12-04T07:56:47.9645708Z class Experiment(NamedTuple): 2025-12-04T07:56:47.9646385Z  rollout_perc: float = ( 2025-12-04T07:56:47.9647151Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T07:56:47.9648246Z  ) 2025-12-04T07:56:47.9648821Z  all_branches: bool = ( 2025-12-04T07:56:47.9649603Z  False # If True, the experiment is also enabled on the exception branches 2025-12-04T07:56:47.9650412Z  ) 2025-12-04T07:56:47.9650875Z  default: bool = ( 2025-12-04T07:56:47.9651593Z  True # If True, the experiment is enabled by default for all queries 2025-12-04T07:56:47.9652287Z  ) 2025-12-04T07:56:47.9652843Z  2025-12-04T07:56:47.9653462Z  # Add more fields as needed 2025-12-04T07:56:47.9654036Z  2025-12-04T07:56:47.9654513Z  2025-12-04T07:56:47.9654990Z class Settings(NamedTuple): 2025-12-04T07:56:47.9655574Z  """ 2025-12-04T07:56:47.9656219Z  Settings for the experiments that can be opted into. 2025-12-04T07:56:47.9656899Z  """ 2025-12-04T07:56:47.9657369Z  2025-12-04T07:56:47.9658269Z  experiments: dict[str, Experiment] = {} 2025-12-04T07:56:47.9658876Z  2025-12-04T07:56:47.9659493Z  2025-12-04T07:56:47.9660087Z class ColorFormatter(logging.Formatter): 2025-12-04T07:56:47.9660975Z  """Color codes the log messages based on the log level""" 2025-12-04T07:56:47.9661782Z  2025-12-04T07:56:47.9662271Z  COLORS = { 2025-12-04T07:56:47.9662813Z  "WARNING": "\033[33m", # Yellow 2025-12-04T07:56:47.9663445Z  "ERROR": "\033[31m", # Red 2025-12-04T07:56:47.9664332Z  "CRITICAL": "\033[31m", # Red 2025-12-04T07:56:47.9665116Z  "INFO": "\033[0m", # Reset 2025-12-04T07:56:47.9665700Z  "DEBUG": "\033[0m", # Reset 2025-12-04T07:56:47.9666352Z  } 2025-12-04T07:56:47.9666779Z  2025-12-04T07:56:47.9667332Z  def format(self, record: LogRecord) -> str: 2025-12-04T07:56:47.9668359Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T07:56:47.9669280Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T07:56:47.9669990Z  return super().format(record) 2025-12-04T07:56:47.9670617Z  2025-12-04T07:56:47.9671057Z  2025-12-04T07:56:47.9671541Z handler = logging.StreamHandler() 2025-12-04T07:56:47.9672449Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T07:56:47.9673248Z  2025-12-04T07:56:47.9673805Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T07:56:47.9761664Z log.addHandler(handler) 2025-12-04T07:56:47.9762601Z log.setLevel(logging.INFO) 2025-12-04T07:56:47.9763515Z  2025-12-04T07:56:47.9764223Z  2025-12-04T07:56:47.9765099Z def set_github_output(key: str, value: str) -> None: 2025-12-04T07:56:47.9766279Z  """ 2025-12-04T07:56:47.9767217Z  Defines outputs of the github action that invokes this script 2025-12-04T07:56:47.9768499Z  """ 2025-12-04T07:56:47.9768937Z  if not GITHUB_OUTPUT: 2025-12-04T07:56:47.9770222Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T07:56:47.9771511Z  log.warning( 2025-12-04T07:56:47.9772437Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T07:56:47.9773391Z  ) 2025-12-04T07:56:47.9773864Z  print(f"::set-output name={key}::{value}") 2025-12-04T07:56:47.9774428Z  return 2025-12-04T07:56:47.9774835Z  2025-12-04T07:56:47.9775235Z  with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T07:56:47.9775839Z  log.info(f"Setting output: {key}='{value}'") 2025-12-04T07:56:47.9776438Z  f.write(f"{key}={value}\n") 2025-12-04T07:56:47.9776937Z  2025-12-04T07:56:47.9777279Z  2025-12-04T07:56:47.9778365Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T07:56:47.9779069Z  return frozenset( 2025-12-04T07:56:47.9779743Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T07:56:47.9780432Z  ) 2025-12-04T07:56:47.9780793Z  2025-12-04T07:56:47.9781132Z  2025-12-04T07:56:47.9781490Z def parse_args() -> Any: 2025-12-04T07:56:47.9782115Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T07:56:47.9783017Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T07:56:47.9783811Z  parser.add_argument( 2025-12-04T07:56:47.9784314Z  "--github-issue-repo", 2025-12-04T07:56:47.9784819Z  type=str, 2025-12-04T07:56:47.9785270Z  required=False, 2025-12-04T07:56:47.9785919Z  default="pytorch/test-infra", 2025-12-04T07:56:47.9786509Z  help="GitHub repo to get the issue", 2025-12-04T07:56:47.9787032Z  ) 2025-12-04T07:56:47.9787422Z  parser.add_argument( 2025-12-04T07:56:47.9788103Z  "--github-repo", 2025-12-04T07:56:47.9788573Z  type=str, 2025-12-04T07:56:47.9789019Z  required=True, 2025-12-04T07:56:47.9789535Z  help="GitHub repo where CI is running", 2025-12-04T07:56:47.9790078Z  ) 2025-12-04T07:56:47.9790463Z  parser.add_argument( 2025-12-04T07:56:47.9791124Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T07:56:47.9791783Z  ) 2025-12-04T07:56:47.9792174Z  parser.add_argument( 2025-12-04T07:56:47.9792842Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T07:56:47.9793540Z  ) 2025-12-04T07:56:47.9793920Z  parser.add_argument( 2025-12-04T07:56:47.9794599Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T07:56:47.9795349Z  ) 2025-12-04T07:56:47.9795781Z  parser.add_argument( 2025-12-04T07:56:47.9796494Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T07:56:47.9797219Z  ) 2025-12-04T07:56:47.9797608Z  parser.add_argument( 2025-12-04T07:56:47.9798376Z  "--github-ref-type", 2025-12-04T07:56:47.9798863Z  type=str, 2025-12-04T07:56:47.9799300Z  required=True, 2025-12-04T07:56:47.9799834Z  help="Current GitHub ref type, branch or tag", 2025-12-04T07:56:47.9800393Z  ) 2025-12-04T07:56:47.9800779Z  parser.add_argument( 2025-12-04T07:56:47.9801424Z  "--eligible-experiments", 2025-12-04T07:56:47.9801980Z  type=_str_comma_separated_to_set, 2025-12-04T07:56:47.9802520Z  required=False, 2025-12-04T07:56:47.9802982Z  default="", 2025-12-04T07:56:47.9803872Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T07:56:47.9804793Z  ) 2025-12-04T07:56:47.9805182Z  parser.add_argument( 2025-12-04T07:56:47.9805673Z  "--opt-out-experiments", 2025-12-04T07:56:47.9806220Z  type=_str_comma_separated_to_set, 2025-12-04T07:56:47.9806752Z  required=False, 2025-12-04T07:56:47.9807213Z  default="", 2025-12-04T07:56:47.9807654Z  help=( 2025-12-04T07:56:47.9809042Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T07:56:47.9810195Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T07:56:47.9811034Z  ), 2025-12-04T07:56:47.9811407Z  ) 2025-12-04T07:56:47.9811806Z  parser.add_argument( 2025-12-04T07:56:47.9812280Z  "--pr-number", 2025-12-04T07:56:47.9812741Z  type=str, 2025-12-04T07:56:47.9813174Z  required=False, 2025-12-04T07:56:47.9813633Z  default="", 2025-12-04T07:56:47.9814161Z  help="the optional PR number where this is run", 2025-12-04T07:56:47.9814731Z  ) 2025-12-04T07:56:47.9815096Z  2025-12-04T07:56:47.9815474Z  return parser.parse_args() 2025-12-04T07:56:47.9815964Z  2025-12-04T07:56:47.9816304Z  2025-12-04T07:56:47.9816922Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T07:56:47.9818174Z  auth = Auth.Token(github_token) 2025-12-04T07:56:47.9818948Z  return Github(auth=auth) 2025-12-04T07:56:47.9819425Z  2025-12-04T07:56:47.9819771Z  2025-12-04T07:56:47.9820447Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T07:56:47.9821251Z  repo = gh.get_repo(repo) 2025-12-04T07:56:47.9821802Z  return repo.get_issue(number=issue_num) 2025-12-04T07:56:47.9822323Z  2025-12-04T07:56:47.9822661Z  2025-12-04T07:56:47.9823024Z def get_potential_pr_author( 2025-12-04T07:56:47.9823708Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T07:56:47.9824392Z ) -> str: 2025-12-04T07:56:47.9824948Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T07:56:47.9825786Z  # Fetch the actual username from the original PR. The PR number is 2025-12-04T07:56:47.9826566Z  # embedded in the tag name: ciflow// 2025-12-04T07:56:47.9827142Z  2025-12-04T07:56:47.9827528Z  gh = get_gh_client(github_token) 2025-12-04T07:56:47.9828372Z  2025-12-04T07:56:47.9828858Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T07:56:47.9829515Z  split_tag = ref_name.split("/") 2025-12-04T07:56:47.9830033Z  if ( 2025-12-04T07:56:47.9830453Z  len(split_tag) == 3 2025-12-04T07:56:47.9830982Z  and split_tag[0] == "ciflow" 2025-12-04T07:56:47.9831538Z  and split_tag[2].isnumeric() 2025-12-04T07:56:47.9832047Z  ): 2025-12-04T07:56:47.9832466Z  pr_number = split_tag[2] 2025-12-04T07:56:47.9832971Z  try: 2025-12-04T07:56:47.9833441Z  repository = gh.get_repo(repo) 2025-12-04T07:56:47.9834220Z  pull = repository.get_pull(number=int(pr_number)) 2025-12-04T07:56:47.9834842Z  except Exception as e: 2025-12-04T07:56:47.9835385Z  raise Exception( # noqa: TRY002 2025-12-04T07:56:47.9836074Z  f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T07:56:47.9836711Z  ) from e 2025-12-04T07:56:47.9837300Z  return pull.user.login # type: ignore[no-any-return] 2025-12-04T07:56:47.9838184Z  # In all other cases, return the original input username 2025-12-04T07:56:47.9838781Z  return username 2025-12-04T07:56:47.9839201Z  2025-12-04T07:56:47.9839530Z  2025-12-04T07:56:47.9839957Z def is_exception_branch(branch: str) -> bool: 2025-12-04T07:56:47.9840495Z  """ 2025-12-04T07:56:47.9841186Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T07:56:47.9841970Z  """ 2025-12-04T07:56:47.9842534Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T07:56:47.9843204Z  2025-12-04T07:56:47.9843537Z  2025-12-04T07:56:47.9843925Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T07:56:47.9844435Z  try: 2025-12-04T07:56:47.9844846Z  data = yaml.safe_load(yaml_text) 2025-12-04T07:56:47.9845364Z  return data 2025-12-04T07:56:47.9845823Z  except yaml.YAMLError: 2025-12-04T07:56:47.9846363Z  log.exception("Error loading YAML") 2025-12-04T07:56:47.9846886Z  raise 2025-12-04T07:56:47.9847283Z  2025-12-04T07:56:47.9847621Z  2025-12-04T07:56:47.9848390Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T07:56:47.9849139Z  """ 2025-12-04T07:56:47.9849906Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T07:56:47.9850666Z  2025-12-04T07:56:47.9851212Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T07:56:47.9851994Z  and the text below is the list of opted in users. 2025-12-04T07:56:47.9852547Z  2025-12-04T07:56:47.9853121Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T07:56:47.9853816Z  """ 2025-12-04T07:56:47.9854292Z  rollout_state_parts = rollout_state.split("---") 2025-12-04T07:56:47.9854908Z  if len(rollout_state_parts) >= 2: 2025-12-04T07:56:47.9855530Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T07:56:47.9856121Z  else: 2025-12-04T07:56:47.9856517Z  return "", rollout_state 2025-12-04T07:56:47.9856996Z  2025-12-04T07:56:47.9857322Z  2025-12-04T07:56:47.9857724Z class UserOptins(dict[str, list[str]]): 2025-12-04T07:56:47.9858342Z  """ 2025-12-04T07:56:47.9858887Z  Dictionary of users with a list of features they have opted into 2025-12-04T07:56:47.9859531Z  """ 2025-12-04T07:56:47.9859888Z  2025-12-04T07:56:47.9860222Z  2025-12-04T07:56:47.9860753Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T07:56:47.9861406Z  """ 2025-12-04T07:56:47.9862143Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-12-04T07:56:47.9862969Z  2025-12-04T07:56:47.9863771Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T07:56:47.9864759Z  - Example line: "@User1,lf,split_build" 2025-12-04T07:56:47.9865587Z  - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T07:56:47.9866218Z  2025-12-04T07:56:47.9866548Z  2025-12-04T07:56:47.9866875Z  """ 2025-12-04T07:56:47.9867259Z  optins = UserOptins() 2025-12-04T07:56:47.9868046Z  for user in user_optin_text.split("\n"): 2025-12-04T07:56:47.9868679Z  user = user.strip("\r\n\t -") 2025-12-04T07:56:47.9869248Z  if not user or not user.startswith("@"): 2025-12-04T07:56:47.9869815Z  # Not a valid user. Skip 2025-12-04T07:56:47.9870315Z  continue 2025-12-04T07:56:47.9870734Z  2025-12-04T07:56:47.9871076Z  if user: 2025-12-04T07:56:47.9871547Z  usr_name = user.split(",")[0].strip("@") 2025-12-04T07:56:47.9872248Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T07:56:47.9872892Z  2025-12-04T07:56:47.9873238Z  return optins 2025-12-04T07:56:47.9873649Z  2025-12-04T07:56:47.9873967Z  2025-12-04T07:56:47.9874461Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T07:56:47.9875070Z  """ 2025-12-04T07:56:47.9875500Z  Check if the experiment name is valid. 2025-12-04T07:56:47.9876043Z  A valid name: 2025-12-04T07:56:47.9876730Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T07:56:47.9877679Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T07:56:47.9878557Z  - Cannot contain spaces 2025-12-04T07:56:47.9879090Z  """ 2025-12-04T07:56:47.9879498Z  2025-12-04T07:56:47.9879981Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T07:56:47.9880705Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T07:56:47.9881432Z  2025-12-04T07:56:47.9881776Z  if valid: 2025-12-04T07:56:47.9882179Z  return True 2025-12-04T07:56:47.9882598Z  2025-12-04T07:56:47.9882935Z  log.error( 2025-12-04T07:56:47.9884730Z  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-04T07:56:47.9886268Z  ) 2025-12-04T07:56:47.9886627Z  return False 2025-12-04T07:56:47.9887038Z  2025-12-04T07:56:47.9887355Z  2025-12-04T07:56:47.9887977Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T07:56:47.9888609Z  """ 2025-12-04T07:56:47.9889219Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T07:56:47.9889933Z  """ 2025-12-04T07:56:47.9890288Z  try: 2025-12-04T07:56:47.9890666Z  if settings_text: 2025-12-04T07:56:47.9891415Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T07:56:47.9892194Z  # for easy reading 2025-12-04T07:56:47.9893022Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T07:56:47.9893909Z  # the backtick character in shell commands. 2025-12-04T07:56:47.9894522Z  backtick = chr(96) # backtick character 2025-12-04T07:56:47.9895203Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T07:56:47.9895873Z  settings = load_yaml(settings_text) 2025-12-04T07:56:47.9896381Z  2025-12-04T07:56:47.9897104Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T07:56:47.9897944Z  experiments = {} 2025-12-04T07:56:47.9898406Z  2025-12-04T07:56:47.9898963Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T07:56:47.9899709Z  if not is_valid_experiment_name(exp_name): 2025-12-04T07:56:47.9900799Z  # 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-04T07:56:47.9901806Z  continue 2025-12-04T07:56:47.9902254Z  2025-12-04T07:56:47.9902621Z  valid_settings = {} 2025-12-04T07:56:47.9903162Z  for setting in exp_settings: 2025-12-04T07:56:47.9903749Z  if setting not in Experiment._fields: 2025-12-04T07:56:47.9904316Z  log.warning( 2025-12-04T07:56:47.9905037Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T07:56:47.9905744Z  ) 2025-12-04T07:56:47.9906198Z  else: 2025-12-04T07:56:47.9906742Z  valid_settings[setting] = exp_settings[setting] 2025-12-04T07:56:47.9907300Z  2025-12-04T07:56:47.9907961Z  experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T07:56:47.9908727Z  return Settings(experiments) 2025-12-04T07:56:47.9909226Z  2025-12-04T07:56:47.9909579Z  except Exception: 2025-12-04T07:56:47.9910092Z  log.exception("Failed to parse settings") 2025-12-04T07:56:47.9910614Z  2025-12-04T07:56:47.9910960Z  return Settings() 2025-12-04T07:56:47.9911384Z  2025-12-04T07:56:47.9911712Z  2025-12-04T07:56:47.9912293Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T07:56:47.9912868Z  """ 2025-12-04T07:56:47.9913325Z  Parse settings, if any, from the rollout state. 2025-12-04T07:56:47.9913869Z  2025-12-04T07:56:47.9914395Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T07:56:47.9915182Z  and the text below is the list of opted in users. 2025-12-04T07:56:47.9915727Z  2025-12-04T07:56:47.9916322Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T07:56:47.9917032Z  """ 2025-12-04T07:56:47.9917608Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T07:56:47.9918626Z  return parse_settings_from_text(settings_text) 2025-12-04T07:56:47.9919167Z  2025-12-04T07:56:47.9919497Z  2025-12-04T07:56:47.9919938Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T07:56:47.9920502Z  """ 2025-12-04T07:56:47.9920911Z  Parse users from the rollout state. 2025-12-04T07:56:47.9921423Z  2025-12-04T07:56:47.9921752Z  """ 2025-12-04T07:56:47.9922302Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T07:56:47.9923058Z  return parse_user_opt_in_from_text(users_text) 2025-12-04T07:56:47.9923591Z  2025-12-04T07:56:47.9923931Z  2025-12-04T07:56:47.9924545Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T07:56:47.9925288Z  """ 2025-12-04T07:56:47.9925731Z  Check if a user is opted into an experiment 2025-12-04T07:56:47.9926273Z  """ 2025-12-04T07:56:47.9926764Z  return experiment_name in user_optins.get(user, []) 2025-12-04T07:56:47.9927592Z  2025-12-04T07:56:47.9928147Z  2025-12-04T07:56:47.9928776Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T07:56:47.9929525Z  """ 2025-12-04T07:56:47.9930014Z  Check if a user explicitly opted out of an experiment 2025-12-04T07:56:47.9930597Z  """ 2025-12-04T07:56:47.9931131Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T07:56:47.9931826Z  experiment_optout = "-" + experiment_name 2025-12-04T07:56:47.9932484Z  if experiment_optout not in user_optins.get(user, []): 2025-12-04T07:56:47.9933073Z  return False 2025-12-04T07:56:47.9933497Z  2025-12-04T07:56:47.9933956Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T07:56:47.9934560Z  log.warning( 2025-12-04T07:56:47.9935388Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T07:56:47.9936243Z  ) 2025-12-04T07:56:47.9936612Z  2025-12-04T07:56:47.9936954Z  return True 2025-12-04T07:56:47.9937351Z  2025-12-04T07:56:47.9937678Z  2025-12-04T07:56:47.9938225Z def get_runner_prefix( 2025-12-04T07:56:47.9938689Z  rollout_state: str, 2025-12-04T07:56:47.9939182Z  workflow_requestors: Iterable[str], 2025-12-04T07:56:47.9939704Z  branch: str, 2025-12-04T07:56:47.9940231Z  eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T07:56:47.9940915Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T07:56:47.9941501Z  is_canary: bool = False, 2025-12-04T07:56:47.9941973Z ) -> str: 2025-12-04T07:56:47.9942413Z  settings = parse_settings(rollout_state) 2025-12-04T07:56:47.9943014Z  user_optins = parse_users(rollout_state) 2025-12-04T07:56:47.9943535Z  2025-12-04T07:56:47.9944009Z  fleet_prefix = "" 2025-12-04T07:56:47.9944472Z  prefixes = [] 2025-12-04T07:56:47.9945165Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T07:56:47.9946123Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T07:56:47.9946825Z  log.info( 2025-12-04T07:56:47.9947527Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T07:56:47.9948530Z  ) 2025-12-04T07:56:47.9948929Z  continue 2025-12-04T07:56:47.9949353Z  2025-12-04T07:56:47.9949718Z  if opt_out_experiments: 2025-12-04T07:56:47.9950284Z  if experiment_name in opt_out_experiments: 2025-12-04T07:56:47.9950934Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T07:56:47.9951526Z  log.info( 2025-12-04T07:56:47.9952467Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T07:56:47.9953419Z  ) 2025-12-04T07:56:47.9953849Z  continue 2025-12-04T07:56:47.9954280Z  2025-12-04T07:56:47.9954649Z  if eligible_experiments: 2025-12-04T07:56:47.9955230Z  if experiment_name not in eligible_experiments: 2025-12-04T07:56:47.9955879Z  exp_list = ", ".join(eligible_experiments) 2025-12-04T07:56:47.9956461Z  log.info( 2025-12-04T07:56:47.9957254Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T07:56:47.9958344Z  ) 2025-12-04T07:56:47.9958922Z  continue 2025-12-04T07:56:47.9959439Z  elif not experiment_settings.default: 2025-12-04T07:56:47.9960024Z  log.info( 2025-12-04T07:56:47.9960763Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T07:56:47.9961493Z  ) 2025-12-04T07:56:47.9961888Z  continue 2025-12-04T07:56:47.9962306Z  2025-12-04T07:56:47.9962767Z  # Is any workflow_requestor opted out to this experiment? 2025-12-04T07:56:47.9963394Z  opted_out_users = [ 2025-12-04T07:56:47.9963869Z  requestor 2025-12-04T07:56:47.9964363Z  for requestor in workflow_requestors 2025-12-04T07:56:47.9965054Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T07:56:47.9965679Z  ] 2025-12-04T07:56:47.9966068Z  2025-12-04T07:56:47.9966423Z  if opted_out_users: 2025-12-04T07:56:47.9966896Z  log.info( 2025-12-04T07:56:47.9967538Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T07:56:47.9968387Z  ) 2025-12-04T07:56:47.9968784Z  continue 2025-12-04T07:56:47.9969195Z  2025-12-04T07:56:47.9969652Z  # Is any workflow_requestor opted in to this experiment? 2025-12-04T07:56:47.9970256Z  opted_in_users = [ 2025-12-04T07:56:47.9970722Z  requestor 2025-12-04T07:56:47.9971201Z  for requestor in workflow_requestors 2025-12-04T07:56:47.9971925Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T07:56:47.9972537Z  ] 2025-12-04T07:56:47.9972904Z  2025-12-04T07:56:47.9973246Z  enabled = False 2025-12-04T07:56:47.9973711Z  if opted_in_users: 2025-12-04T07:56:47.9974336Z  log.info( 2025-12-04T07:56:47.9974976Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T07:56:47.9975647Z  ) 2025-12-04T07:56:47.9976050Z  enabled = True 2025-12-04T07:56:47.9976497Z  2025-12-04T07:56:47.9976893Z  elif experiment_settings.rollout_perc: 2025-12-04T07:56:47.9977713Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T07:56:47.9978749Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T07:56:47.9979391Z  log.info( 2025-12-04T07:56:47.9980276Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T07:56:47.9981174Z  ) 2025-12-04T07:56:47.9981605Z  enabled = True 2025-12-04T07:56:47.9982068Z  2025-12-04T07:56:47.9982408Z  if enabled: 2025-12-04T07:56:47.9982872Z  label = experiment_name 2025-12-04T07:56:47.9983435Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T07:56:47.9984261Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T07:56:47.9985134Z  # - If it's enabled, then we always list it's prefix first 2025-12-04T07:56:47.9985894Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T07:56:47.9986555Z  if is_canary: 2025-12-04T07:56:47.9987069Z  label += CANARY_FLEET_SUFFIX 2025-12-04T07:56:47.9987616Z  fleet_prefix = label 2025-12-04T07:56:47.9988522Z  else: 2025-12-04T07:56:47.9988989Z  prefixes.append(label) 2025-12-04T07:56:47.9989494Z  2025-12-04T07:56:47.9989847Z  if len(prefixes) > 1: 2025-12-04T07:56:47.9990318Z  log.error( 2025-12-04T07:56:47.9991363Z  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-04T07:56:47.9992436Z  ) 2025-12-04T07:56:47.9992846Z  prefixes = prefixes[:1] 2025-12-04T07:56:47.9993319Z  2025-12-04T07:56:47.9993697Z  # Fleet always comes first 2025-12-04T07:56:47.9994184Z  if fleet_prefix: 2025-12-04T07:56:47.9994674Z  prefixes.insert(0, fleet_prefix) 2025-12-04T07:56:47.9995176Z  2025-12-04T07:56:47.9995620Z  return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T07:56:47.9996183Z  2025-12-04T07:56:47.9996508Z  2025-12-04T07:56:47.9997143Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T07:56:47.9998108Z  """ 2025-12-04T07:56:47.9998708Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T07:56:47.9999434Z  2025-12-04T07:56:48.0000054Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T07:56:48.0000753Z  """ 2025-12-04T07:56:48.0001155Z  gh = get_gh_client(github_token) 2025-12-04T07:56:48.0001712Z  issue = get_issue(gh, repo, issue_num) 2025-12-04T07:56:48.0002354Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T07:56:48.0002933Z  2025-12-04T07:56:48.0003256Z  2025-12-04T07:56:48.0003840Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T07:56:48.0004740Z  for _ in range(num_retries): 2025-12-04T07:56:48.0005265Z  try: 2025-12-04T07:56:48.0005721Z  req = Request(url=url, headers=headers) 2025-12-04T07:56:48.0006378Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T07:56:48.0007018Z  return json.loads(content) 2025-12-04T07:56:48.0007538Z  except Exception as e: 2025-12-04T07:56:48.0008238Z  log.warning(f"Could not download {url}: {e}") 2025-12-04T07:56:48.0008963Z  2025-12-04T07:56:48.0009541Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T07:56:48.0010246Z  return {} 2025-12-04T07:56:48.0010635Z  2025-12-04T07:56:48.0010963Z  2025-12-04T07:56:48.0011287Z @cache 2025-12-04T07:56:48.0011920Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T07:56:48.0012664Z  """ 2025-12-04T07:56:48.0013071Z  Dynamically get PR information 2025-12-04T07:56:48.0013556Z  """ 2025-12-04T07:56:48.0014074Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T07:56:48.0014703Z  headers = { 2025-12-04T07:56:48.0015190Z  "Accept": "application/vnd.github.v3+json", 2025-12-04T07:56:48.0015804Z  "Authorization": f"token {github_token}", 2025-12-04T07:56:48.0016322Z  } 2025-12-04T07:56:48.0016769Z  json_response: dict[str, Any] = download_json( 2025-12-04T07:56:48.0017371Z  url=f"{github_api}/issues/{pr_number}", 2025-12-04T07:56:48.0018032Z  headers=headers, 2025-12-04T07:56:48.0018480Z  ) 2025-12-04T07:56:48.0018824Z  2025-12-04T07:56:48.0019184Z  if not json_response: 2025-12-04T07:56:48.0019937Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T07:56:48.0020566Z  return {} 2025-12-04T07:56:48.0020972Z  2025-12-04T07:56:48.0021330Z  return json_response 2025-12-04T07:56:48.0021767Z  2025-12-04T07:56:48.0022090Z  2025-12-04T07:56:48.0022675Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T07:56:48.0023391Z  """ 2025-12-04T07:56:48.0023942Z  Dynamically get the latest list of labels from the pull request 2025-12-04T07:56:48.0024572Z  """ 2025-12-04T07:56:48.0025063Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T07:56:48.0025660Z  return { 2025-12-04T07:56:48.0026260Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T07:56:48.0026941Z  } 2025-12-04T07:56:48.0027288Z  2025-12-04T07:56:48.0027615Z  2025-12-04T07:56:48.0028240Z def main() -> None: 2025-12-04T07:56:48.0028697Z  args = parse_args() 2025-12-04T07:56:48.0029132Z  2025-12-04T07:56:48.0029547Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T07:56:48.0030067Z  2025-12-04T07:56:48.0030434Z  # Check if the PR is opt-out 2025-12-04T07:56:48.0030930Z  if args.pr_number: 2025-12-04T07:56:48.0031612Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T07:56:48.0032351Z  if OPT_OUT_LABEL in labels: 2025-12-04T07:56:48.0032852Z  log.info( 2025-12-04T07:56:48.0033570Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T07:56:48.0034315Z  ) 2025-12-04T07:56:48.0034895Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T07:56:48.0035570Z  sys.exit() 2025-12-04T07:56:48.0036128Z  2025-12-04T07:56:48.0036467Z  try: 2025-12-04T07:56:48.0036916Z  rollout_state = get_rollout_state_from_issue( 2025-12-04T07:56:48.0037635Z  args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T07:56:48.0038490Z  ) 2025-12-04T07:56:48.0038862Z  2025-12-04T07:56:48.0039255Z  username = get_potential_pr_author( 2025-12-04T07:56:48.0039791Z  args.github_token, 2025-12-04T07:56:48.0040334Z  args.github_repo, 2025-12-04T07:56:48.0040823Z  args.github_actor, 2025-12-04T07:56:48.0041325Z  args.github_ref_type, 2025-12-04T07:56:48.0041827Z  args.github_branch, 2025-12-04T07:56:48.0042298Z  ) 2025-12-04T07:56:48.0042655Z  2025-12-04T07:56:48.0043137Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T07:56:48.0043725Z  2025-12-04T07:56:48.0044130Z  runner_label_prefix = get_runner_prefix( 2025-12-04T07:56:48.0044687Z  rollout_state, 2025-12-04T07:56:48.0045202Z  (args.github_issue_owner, username), 2025-12-04T07:56:48.0045750Z  args.github_branch, 2025-12-04T07:56:48.0046274Z  args.eligible_experiments, 2025-12-04T07:56:48.0046819Z  args.opt_out_experiments, 2025-12-04T07:56:48.0047329Z  is_canary, 2025-12-04T07:56:48.0047863Z  ) 2025-12-04T07:56:48.0048237Z  2025-12-04T07:56:48.0048594Z  except Exception as e: 2025-12-04T07:56:48.0049070Z  log.error( 2025-12-04T07:56:48.0049758Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T07:56:48.0050643Z  ) 2025-12-04T07:56:48.0051013Z  2025-12-04T07:56:48.0051570Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T07:56:48.0052217Z  2025-12-04T07:56:48.0052541Z  2025-12-04T07:56:48.0052993Z if __name__ == "__main__": 2025-12-04T07:56:48.0053456Z  main() 2025-12-04T07:56:48.0053828Z  2025-12-04T07:56:48.0054151Z EOF 2025-12-04T07:56:48.0054489Z  2025-12-04T07:56:48.0054841Z cat runner_determinator.py 2025-12-04T07:56:48.4190246Z shell: /usr/bin/bash -e {0} 2025-12-04T07:56:48.4191421Z env: 2025-12-04T07:56:48.4192434Z GITHUB_TOKEN: *** 2025-12-04T07:56:48.4193160Z ISSUE_NUMBER: 5132 2025-12-04T07:56:48.4193937Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T07:56:48.4194803Z ISSUE_OWNER: 2025-12-04T07:56:48.4195494Z CHECK_EXPERIMENTS: 2025-12-04T07:56:48.4196229Z OPT_OUT_EXPERIMENTS: 2025-12-04T07:56:48.4196992Z PR_NUMBER: 2025-12-04T07:56:48.4197659Z ##[endgroup] 2025-12-04T07:56:48.4420784Z # flake8: noqa: G004 2025-12-04T07:56:48.4421174Z 2025-12-04T07:56:48.4421683Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T07:56:48.4422812Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T07:56:48.4423768Z # python .github/scripts/update_runner_determinator.py 2025-12-04T07:56:48.4424314Z 2025-12-04T07:56:48.4424504Z """ 2025-12-04T07:56:48.4425173Z This runner determinator is used to determine which set of runners to run a 2025-12-04T07:56:48.4426215Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T07:56:48.4427284Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T07:56:48.4428560Z of which runners should be used to run which job. 2025-12-04T07:56:48.4429031Z 2025-12-04T07:56:48.4429451Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T07:56:48.4430691Z separated by a line containing "---". If the line is not present, the 2025-12-04T07:56:48.4431700Z settings are considered to be empty with only the second part, the user 2025-12-04T07:56:48.4432473Z list, defined. 2025-12-04T07:56:48.4432729Z 2025-12-04T07:56:48.4433132Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T07:56:48.4434174Z used to define any settings that are needed to determine which runners to use. 2025-12-04T07:56:48.4435115Z It's fields are defined by the RolloutSettings class below. 2025-12-04T07:56:48.4435613Z 2025-12-04T07:56:48.4436025Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T07:56:48.4436989Z The user list is also a comma separated list of additional features or 2025-12-04T07:56:48.4438024Z experiments which the user could be opted in to. 2025-12-04T07:56:48.4438556Z 2025-12-04T07:56:48.4438780Z The user list has the following rules: 2025-12-04T07:56:48.4439202Z 2025-12-04T07:56:48.4439542Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T07:56:48.4440495Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T07:56:48.4441344Z - A "#" prefix opts the user out of all experiments 2025-12-04T07:56:48.4441784Z 2025-12-04T07:56:48.4441975Z Example config: 2025-12-04T07:56:48.4442460Z # A list of experiments that can be opted into. 2025-12-04T07:56:48.4443199Z # This defines the behavior they'll induce when opted into. 2025-12-04T07:56:48.4443888Z # Expected syntax is: 2025-12-04T07:56:48.4444602Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T07:56:48.4445696Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T07:56:48.4446396Z 2025-12-04T07:56:48.4446578Z experiments: 2025-12-04T07:56:48.4447003Z lf: 2025-12-04T07:56:48.4447406Z rollout_percent: 25 2025-12-04T07:56:48.4448350Z all_branches: false 2025-12-04T07:56:48.4448851Z default: true 2025-12-04T07:56:48.4449275Z --- 2025-12-04T07:56:48.4449483Z 2025-12-04T07:56:48.4449653Z # Opt-ins: 2025-12-04T07:56:48.4450256Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T07:56:48.4451162Z # and specifying experiments to enable in a comma-separated list. 2025-12-04T07:56:48.4451980Z # To always opt out of an experiment, prefix it with a "-". 2025-12-04T07:56:48.4452661Z # Experiments should be from the above list. 2025-12-04T07:56:48.4453058Z 2025-12-04T07:56:48.4453243Z @User1,-lf,split_build 2025-12-04T07:56:48.4453696Z @User2,lf 2025-12-04T07:56:48.4454085Z @User3,split_build 2025-12-04T07:56:48.4454509Z """ 2025-12-04T07:56:48.4454705Z 2025-12-04T07:56:48.4454874Z import json 2025-12-04T07:56:48.4455258Z import logging 2025-12-04T07:56:48.4455642Z import os 2025-12-04T07:56:48.4456017Z import random 2025-12-04T07:56:48.4456400Z import re 2025-12-04T07:56:48.4456770Z import sys 2025-12-04T07:56:48.4457187Z from argparse import ArgumentParser 2025-12-04T07:56:48.4457731Z from collections.abc import Iterable 2025-12-04T07:56:48.4458402Z from functools import cache 2025-12-04T07:56:48.4458886Z from logging import LogRecord 2025-12-04T07:56:48.4459395Z from typing import Any, NamedTuple 2025-12-04T07:56:48.4459947Z from urllib.request import Request, urlopen 2025-12-04T07:56:48.4460344Z 2025-12-04T07:56:48.4460509Z import yaml 2025-12-04T07:56:48.4460907Z from github import Auth, Github 2025-12-04T07:56:48.4461414Z from github.Issue import Issue 2025-12-04T07:56:48.4461728Z 2025-12-04T07:56:48.4461735Z 2025-12-04T07:56:48.4461963Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T07:56:48.4462674Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T07:56:48.4463593Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T07:56:48.4464188Z 2025-12-04T07:56:48.4464424Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T07:56:48.4465180Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T07:56:48.4465714Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T07:56:48.4466286Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T07:56:48.4466655Z 2025-12-04T07:56:48.4466862Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T07:56:48.4467211Z 2025-12-04T07:56:48.4467395Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T07:56:48.4468091Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T07:56:48.4468396Z 2025-12-04T07:56:48.4468402Z 2025-12-04T07:56:48.4468609Z class Experiment(NamedTuple): 2025-12-04T07:56:48.4469104Z rollout_perc: float = ( 2025-12-04T07:56:48.4469758Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T07:56:48.4470477Z ) 2025-12-04T07:56:48.4470856Z all_branches: bool = ( 2025-12-04T07:56:48.4471494Z False # If True, the experiment is also enabled on the exception branches 2025-12-04T07:56:48.4472216Z ) 2025-12-04T07:56:48.4472579Z default: bool = ( 2025-12-04T07:56:48.4473176Z True # If True, the experiment is enabled by default for all queries 2025-12-04T07:56:48.4473843Z ) 2025-12-04T07:56:48.4474050Z 2025-12-04T07:56:48.4474234Z # Add more fields as needed 2025-12-04T07:56:48.4474546Z 2025-12-04T07:56:48.4474553Z 2025-12-04T07:56:48.4474840Z class Settings(NamedTuple): 2025-12-04T07:56:48.4475300Z """ 2025-12-04T07:56:48.4475768Z Settings for the experiments that can be opted into. 2025-12-04T07:56:48.4476360Z """ 2025-12-04T07:56:48.4476559Z 2025-12-04T07:56:48.4476775Z experiments: dict[str, Experiment] = {} 2025-12-04T07:56:48.4477152Z 2025-12-04T07:56:48.4477159Z 2025-12-04T07:56:48.4477373Z class ColorFormatter(logging.Formatter): 2025-12-04T07:56:48.4478134Z """Color codes the log messages based on the log level""" 2025-12-04T07:56:48.4478592Z 2025-12-04T07:56:48.4478767Z COLORS = { 2025-12-04T07:56:48.4479177Z "WARNING": "\033[33m", # Yellow 2025-12-04T07:56:48.4479875Z "ERROR": "\033[31m", # Red 2025-12-04T07:56:48.4480384Z "CRITICAL": "\033[31m", # Red 2025-12-04T07:56:48.4480900Z "INFO": "\033[0m", # Reset 2025-12-04T07:56:48.4481396Z "DEBUG": "\033[0m", # Reset 2025-12-04T07:56:48.4481882Z } 2025-12-04T07:56:48.4482082Z 2025-12-04T07:56:48.4482304Z def format(self, record: LogRecord) -> str: 2025-12-04T07:56:48.4483089Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T07:56:48.4483906Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T07:56:48.4484504Z return super().format(record) 2025-12-04T07:56:48.4484850Z 2025-12-04T07:56:48.4484856Z 2025-12-04T07:56:48.4485063Z handler = logging.StreamHandler() 2025-12-04T07:56:48.4485794Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T07:56:48.4486390Z 2025-12-04T07:56:48.4486640Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T07:56:48.4487243Z log.addHandler(handler) 2025-12-04T07:56:48.4487706Z log.setLevel(logging.INFO) 2025-12-04T07:56:48.4488244Z 2025-12-04T07:56:48.4488251Z 2025-12-04T07:56:48.4488550Z def set_github_output(key: str, value: str) -> None: 2025-12-04T07:56:48.4489142Z """ 2025-12-04T07:56:48.4489667Z Defines outputs of the github action that invokes this script 2025-12-04T07:56:48.4490317Z """ 2025-12-04T07:56:48.4490702Z if not GITHUB_OUTPUT: 2025-12-04T07:56:48.4491829Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T07:56:48.4493061Z log.warning( 2025-12-04T07:56:48.4493965Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T07:56:48.4494947Z ) 2025-12-04T07:56:48.4504907Z print(f"::set-output name={key}::{value}") 2025-12-04T07:56:48.4505548Z return 2025-12-04T07:56:48.4505808Z 2025-12-04T07:56:48.4506191Z with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T07:56:48.4506819Z log.info(f"Setting output: {key}='{value}'") 2025-12-04T07:56:48.4507434Z f.write(f"{key}={value}\n") 2025-12-04T07:56:48.4508032Z 2025-12-04T07:56:48.4508047Z 2025-12-04T07:56:48.4508410Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T07:56:48.4509082Z return frozenset( 2025-12-04T07:56:48.4509718Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T07:56:48.4510438Z ) 2025-12-04T07:56:48.4510650Z 2025-12-04T07:56:48.4510658Z 2025-12-04T07:56:48.4510845Z def parse_args() -> Any: 2025-12-04T07:56:48.4511412Z parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T07:56:48.4512318Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T07:56:48.4513137Z parser.add_argument( 2025-12-04T07:56:48.4513601Z "--github-issue-repo", 2025-12-04T07:56:48.4514088Z type=str, 2025-12-04T07:56:48.4514497Z required=False, 2025-12-04T07:56:48.4514963Z default="pytorch/test-infra", 2025-12-04T07:56:48.4515513Z help="GitHub repo to get the issue", 2025-12-04T07:56:48.4516209Z ) 2025-12-04T07:56:48.4516677Z parser.add_argument( 2025-12-04T07:56:48.4572057Z "--github-repo", 2025-12-04T07:56:48.4572868Z type=str, 2025-12-04T07:56:48.4573578Z required=True, 2025-12-04T07:56:48.4574121Z help="GitHub repo where CI is running", 2025-12-04T07:56:48.4574749Z ) 2025-12-04T07:56:48.4575173Z parser.add_argument( 2025-12-04T07:56:48.4575888Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T07:56:48.4576671Z ) 2025-12-04T07:56:48.4577088Z parser.add_argument( 2025-12-04T07:56:48.4577934Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T07:56:48.4578748Z ) 2025-12-04T07:56:48.4579383Z parser.add_argument( 2025-12-04T07:56:48.4580130Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T07:56:48.4580965Z ) 2025-12-04T07:56:48.4581378Z parser.add_argument( 2025-12-04T07:56:48.4582138Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T07:56:48.4582983Z ) 2025-12-04T07:56:48.4583396Z parser.add_argument( 2025-12-04T07:56:48.4583907Z "--github-ref-type", 2025-12-04T07:56:48.4584431Z type=str, 2025-12-04T07:56:48.4584880Z required=True, 2025-12-04T07:56:48.4585433Z help="Current GitHub ref type, branch or tag", 2025-12-04T07:56:48.4586077Z ) 2025-12-04T07:56:48.4586487Z parser.add_argument( 2025-12-04T07:56:48.4587008Z "--eligible-experiments", 2025-12-04T07:56:48.4587588Z type=_str_comma_separated_to_set, 2025-12-04T07:56:48.4588310Z required=False, 2025-12-04T07:56:48.4588812Z default="", 2025-12-04T07:56:48.4589779Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T07:56:48.4590824Z ) 2025-12-04T07:56:48.4591217Z parser.add_argument( 2025-12-04T07:56:48.4591702Z "--opt-out-experiments", 2025-12-04T07:56:48.4592246Z type=_str_comma_separated_to_set, 2025-12-04T07:56:48.4592815Z required=False, 2025-12-04T07:56:48.4593260Z default="", 2025-12-04T07:56:48.4593688Z help=( 2025-12-04T07:56:48.4594425Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T07:56:48.4595711Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T07:56:48.4596650Z ), 2025-12-04T07:56:48.4597038Z ) 2025-12-04T07:56:48.4597426Z parser.add_argument( 2025-12-04T07:56:48.4598032Z "--pr-number", 2025-12-04T07:56:48.4598482Z type=str, 2025-12-04T07:56:48.4598918Z required=False, 2025-12-04T07:56:48.4599371Z default="", 2025-12-04T07:56:48.4600035Z help="the optional PR number where this is run", 2025-12-04T07:56:48.4600670Z ) 2025-12-04T07:56:48.4600884Z 2025-12-04T07:56:48.4601102Z return parser.parse_args() 2025-12-04T07:56:48.4601443Z 2025-12-04T07:56:48.4601449Z 2025-12-04T07:56:48.4601895Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T07:56:48.4602737Z auth = Auth.Token(github_token) 2025-12-04T07:56:48.4603291Z return Github(auth=auth) 2025-12-04T07:56:48.4603611Z 2025-12-04T07:56:48.4603617Z 2025-12-04T07:56:48.4604142Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T07:56:48.4605034Z repo = gh.get_repo(repo) 2025-12-04T07:56:48.4605582Z return repo.get_issue(number=issue_num) 2025-12-04T07:56:48.4606005Z 2025-12-04T07:56:48.4606011Z 2025-12-04T07:56:48.4606213Z def get_potential_pr_author( 2025-12-04T07:56:48.4606931Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T07:56:48.4607687Z ) -> str: 2025-12-04T07:56:48.4608372Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T07:56:48.4609268Z # Fetch the actual username from the original PR. The PR number is 2025-12-04T07:56:48.4610083Z # embedded in the tag name: ciflow// 2025-12-04T07:56:48.4610553Z 2025-12-04T07:56:48.4610757Z gh = get_gh_client(github_token) 2025-12-04T07:56:48.4611124Z 2025-12-04T07:56:48.4611411Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T07:56:48.4612104Z split_tag = ref_name.split("/") 2025-12-04T07:56:48.4612656Z if ( 2025-12-04T07:56:48.4613073Z len(split_tag) == 3 2025-12-04T07:56:48.4613601Z and split_tag[0] == "ciflow" 2025-12-04T07:56:48.4614171Z and split_tag[2].isnumeric() 2025-12-04T07:56:48.4614715Z ): 2025-12-04T07:56:48.4615274Z pr_number = split_tag[2] 2025-12-04T07:56:48.4615801Z try: 2025-12-04T07:56:48.4616258Z repository = gh.get_repo(repo) 2025-12-04T07:56:48.4616937Z pull = repository.get_pull(number=int(pr_number)) 2025-12-04T07:56:48.4617606Z except Exception as e: 2025-12-04T07:56:48.4618297Z raise Exception( # noqa: TRY002 2025-12-04T07:56:48.4619056Z f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T07:56:48.4619749Z ) from e 2025-12-04T07:56:48.4620311Z return pull.user.login # type: ignore[no-any-return] 2025-12-04T07:56:48.4621032Z # In all other cases, return the original input username 2025-12-04T07:56:48.4621655Z return username 2025-12-04T07:56:48.4621903Z 2025-12-04T07:56:48.4621910Z 2025-12-04T07:56:48.4622141Z def is_exception_branch(branch: str) -> bool: 2025-12-04T07:56:48.4622701Z """ 2025-12-04T07:56:48.4623376Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T07:56:48.4624204Z """ 2025-12-04T07:56:48.4624768Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T07:56:48.4625327Z 2025-12-04T07:56:48.4625334Z 2025-12-04T07:56:48.4625535Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T07:56:48.4626055Z try: 2025-12-04T07:56:48.4626449Z data = yaml.safe_load(yaml_text) 2025-12-04T07:56:48.4626979Z return data 2025-12-04T07:56:48.4627402Z except yaml.YAMLError: 2025-12-04T07:56:48.4628014Z log.exception("Error loading YAML") 2025-12-04T07:56:48.4628552Z raise 2025-12-04T07:56:48.4628775Z 2025-12-04T07:56:48.4628782Z 2025-12-04T07:56:48.4629216Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T07:56:48.4630014Z """ 2025-12-04T07:56:48.4630658Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T07:56:48.4631315Z 2025-12-04T07:56:48.4631809Z If the issue body contains "---" then the text above that is the settings 2025-12-04T07:56:48.4632622Z and the text below is the list of opted in users. 2025-12-04T07:56:48.4633056Z 2025-12-04T07:56:48.4633446Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T07:56:48.4634194Z """ 2025-12-04T07:56:48.4634648Z rollout_state_parts = rollout_state.split("---") 2025-12-04T07:56:48.4635278Z if len(rollout_state_parts) >= 2: 2025-12-04T07:56:48.4635904Z return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T07:56:48.4636519Z else: 2025-12-04T07:56:48.4636902Z return "", rollout_state 2025-12-04T07:56:48.4637230Z 2025-12-04T07:56:48.4637237Z 2025-12-04T07:56:48.4637445Z class UserOptins(dict[str, list[str]]): 2025-12-04T07:56:48.4638357Z """ 2025-12-04T07:56:48.4638885Z Dictionary of users with a list of features they have opted into 2025-12-04T07:56:48.4639572Z """ 2025-12-04T07:56:48.4639769Z 2025-12-04T07:56:48.4639775Z 2025-12-04T07:56:48.4640128Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T07:56:48.4640838Z """ 2025-12-04T07:56:48.4641577Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-12-04T07:56:48.4642327Z 2025-12-04T07:56:48.4642993Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T07:56:48.4644059Z - Example line: "@User1,lf,split_build" 2025-12-04T07:56:48.4644778Z - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T07:56:48.4645295Z 2025-12-04T07:56:48.4645302Z 2025-12-04T07:56:48.4645461Z """ 2025-12-04T07:56:48.4645840Z optins = UserOptins() 2025-12-04T07:56:48.4646343Z for user in user_optin_text.split("\n"): 2025-12-04T07:56:48.4646921Z user = user.strip("\r\n\t -") 2025-12-04T07:56:48.4647624Z if not user or not user.startswith("@"): 2025-12-04T07:56:48.4648864Z # Not a valid user. Skip 2025-12-04T07:56:48.4649394Z continue 2025-12-04T07:56:48.4649643Z 2025-12-04T07:56:48.4649813Z if user: 2025-12-04T07:56:48.4650255Z usr_name = user.split(",")[0].strip("@") 2025-12-04T07:56:48.4651006Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T07:56:48.4651533Z 2025-12-04T07:56:48.4651704Z return optins 2025-12-04T07:56:48.4651954Z 2025-12-04T07:56:48.4651960Z 2025-12-04T07:56:48.4652266Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T07:56:48.4652902Z """ 2025-12-04T07:56:48.4653307Z Check if the experiment name is valid. 2025-12-04T07:56:48.4653858Z A valid name: 2025-12-04T07:56:48.4654509Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T07:56:48.4655505Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T07:56:48.4656268Z - Cannot contain spaces 2025-12-04T07:56:48.4656741Z """ 2025-12-04T07:56:48.4656940Z 2025-12-04T07:56:48.4657208Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T07:56:48.4658085Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T07:56:48.4658555Z 2025-12-04T07:56:48.4658727Z if valid: 2025-12-04T07:56:48.4659113Z return True 2025-12-04T07:56:48.4659363Z 2025-12-04T07:56:48.4659527Z log.error( 2025-12-04T07:56:48.4661106Z 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-04T07:56:48.4662819Z ) 2025-12-04T07:56:48.4663180Z return False 2025-12-04T07:56:48.4663420Z 2025-12-04T07:56:48.4663427Z 2025-12-04T07:56:48.4663734Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T07:56:48.4664387Z """ 2025-12-04T07:56:48.4665179Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T07:56:48.4665951Z """ 2025-12-04T07:56:48.4666307Z try: 2025-12-04T07:56:48.4666688Z if settings_text: 2025-12-04T07:56:48.4667429Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T07:56:48.4668393Z # for easy reading 2025-12-04T07:56:48.4669221Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T07:56:48.4670161Z # the backtick character in shell commands. 2025-12-04T07:56:48.4670791Z backtick = chr(96) # backtick character 2025-12-04T07:56:48.4671472Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T07:56:48.4672153Z settings = load_yaml(settings_text) 2025-12-04T07:56:48.4672538Z 2025-12-04T07:56:48.4672973Z # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T07:56:48.4673754Z experiments = {} 2025-12-04T07:56:48.4674054Z 2025-12-04T07:56:48.4674451Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T07:56:48.4675235Z if not is_valid_experiment_name(exp_name): 2025-12-04T07:56:48.4676407Z # 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-04T07:56:48.4677518Z continue 2025-12-04T07:56:48.4677921Z 2025-12-04T07:56:48.4678108Z valid_settings = {} 2025-12-04T07:56:48.4678644Z for setting in exp_settings: 2025-12-04T07:56:48.4679236Z if setting not in Experiment._fields: 2025-12-04T07:56:48.4679812Z log.warning( 2025-12-04T07:56:48.4680544Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T07:56:48.4681448Z ) 2025-12-04T07:56:48.4681888Z else: 2025-12-04T07:56:48.4682419Z valid_settings[setting] = exp_settings[setting] 2025-12-04T07:56:48.4682864Z 2025-12-04T07:56:48.4683152Z experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T07:56:48.4683806Z return Settings(experiments) 2025-12-04T07:56:48.4684165Z 2025-12-04T07:56:48.4684350Z except Exception: 2025-12-04T07:56:48.4684829Z log.exception("Failed to parse settings") 2025-12-04T07:56:48.4685240Z 2025-12-04T07:56:48.4685411Z return Settings() 2025-12-04T07:56:48.4685671Z 2025-12-04T07:56:48.4685677Z 2025-12-04T07:56:48.4685933Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T07:56:48.4686516Z """ 2025-12-04T07:56:48.4686952Z Parse settings, if any, from the rollout state. 2025-12-04T07:56:48.4687371Z 2025-12-04T07:56:48.4687840Z If the issue body contains "---" then the text above that is the settings 2025-12-04T07:56:48.4688673Z and the text below is the list of opted in users. 2025-12-04T07:56:48.4689093Z 2025-12-04T07:56:48.4689514Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T07:56:48.4690291Z """ 2025-12-04T07:56:48.4690858Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T07:56:48.4691638Z return parse_settings_from_text(settings_text) 2025-12-04T07:56:48.4692054Z 2025-12-04T07:56:48.4692061Z 2025-12-04T07:56:48.4692318Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T07:56:48.4692894Z """ 2025-12-04T07:56:48.4693294Z Parse users from the rollout state. 2025-12-04T07:56:48.4693658Z 2025-12-04T07:56:48.4693817Z """ 2025-12-04T07:56:48.4694356Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T07:56:48.4695128Z return parse_user_opt_in_from_text(users_text) 2025-12-04T07:56:48.4695548Z 2025-12-04T07:56:48.4695555Z 2025-12-04T07:56:48.4696123Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T07:56:48.4696919Z """ 2025-12-04T07:56:48.4697341Z Check if a user is opted into an experiment 2025-12-04T07:56:48.4698022Z """ 2025-12-04T07:56:48.4698475Z return experiment_name in user_optins.get(user, []) 2025-12-04T07:56:48.4698934Z 2025-12-04T07:56:48.4698941Z 2025-12-04T07:56:48.4699382Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T07:56:48.4700181Z """ 2025-12-04T07:56:48.4700637Z Check if a user explicitly opted out of an experiment 2025-12-04T07:56:48.4701241Z """ 2025-12-04T07:56:48.4701748Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T07:56:48.4702466Z experiment_optout = "-" + experiment_name 2025-12-04T07:56:48.4703126Z if experiment_optout not in user_optins.get(user, []): 2025-12-04T07:56:48.4703772Z return False 2025-12-04T07:56:48.4704028Z 2025-12-04T07:56:48.4704310Z if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T07:56:48.4704943Z log.warning( 2025-12-04T07:56:48.4705811Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T07:56:48.4706737Z ) 2025-12-04T07:56:48.4706955Z 2025-12-04T07:56:48.4707122Z return True 2025-12-04T07:56:48.4707353Z 2025-12-04T07:56:48.4707359Z 2025-12-04T07:56:48.4707537Z def get_runner_prefix( 2025-12-04T07:56:48.4708089Z rollout_state: str, 2025-12-04T07:56:48.4708569Z workflow_requestors: Iterable[str], 2025-12-04T07:56:48.4709095Z branch: str, 2025-12-04T07:56:48.4709581Z eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T07:56:48.4710252Z opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T07:56:48.4710857Z is_canary: bool = False, 2025-12-04T07:56:48.4711459Z ) -> str: 2025-12-04T07:56:48.4711871Z settings = parse_settings(rollout_state) 2025-12-04T07:56:48.4712458Z user_optins = parse_users(rollout_state) 2025-12-04T07:56:48.4712846Z 2025-12-04T07:56:48.4713022Z fleet_prefix = "" 2025-12-04T07:56:48.4713447Z prefixes = [] 2025-12-04T07:56:48.4714083Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T07:56:48.4715064Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T07:56:48.4715800Z log.info( 2025-12-04T07:56:48.4716496Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T07:56:48.4717282Z ) 2025-12-04T07:56:48.4717652Z continue 2025-12-04T07:56:48.4718008Z 2025-12-04T07:56:48.4718191Z if opt_out_experiments: 2025-12-04T07:56:48.4718726Z if experiment_name in opt_out_experiments: 2025-12-04T07:56:48.4719380Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T07:56:48.4719989Z log.info( 2025-12-04T07:56:48.4720927Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T07:56:48.4721939Z ) 2025-12-04T07:56:48.4722330Z continue 2025-12-04T07:56:48.4722596Z 2025-12-04T07:56:48.4722779Z if eligible_experiments: 2025-12-04T07:56:48.4723333Z if experiment_name not in eligible_experiments: 2025-12-04T07:56:48.4723973Z exp_list = ", ".join(eligible_experiments) 2025-12-04T07:56:48.4724532Z log.info( 2025-12-04T07:56:48.4725323Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T07:56:48.4726205Z ) 2025-12-04T07:56:48.4726600Z continue 2025-12-04T07:56:48.4727065Z elif not experiment_settings.default: 2025-12-04T07:56:48.4727602Z log.info( 2025-12-04T07:56:48.4728488Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T07:56:48.4729256Z ) 2025-12-04T07:56:48.4729632Z continue 2025-12-04T07:56:48.4729883Z 2025-12-04T07:56:48.4730160Z # Is any workflow_requestor opted out to this experiment? 2025-12-04T07:56:48.4730795Z opted_out_users = [ 2025-12-04T07:56:48.4731238Z requestor 2025-12-04T07:56:48.4731695Z for requestor in workflow_requestors 2025-12-04T07:56:48.4732376Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T07:56:48.4733018Z ] 2025-12-04T07:56:48.4733223Z 2025-12-04T07:56:48.4733401Z if opted_out_users: 2025-12-04T07:56:48.4733858Z log.info( 2025-12-04T07:56:48.4734489Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T07:56:48.4735202Z ) 2025-12-04T07:56:48.4735590Z continue 2025-12-04T07:56:48.4735838Z 2025-12-04T07:56:48.4736114Z # Is any workflow_requestor opted in to this experiment? 2025-12-04T07:56:48.4736741Z opted_in_users = [ 2025-12-04T07:56:48.4737188Z requestor 2025-12-04T07:56:48.4737654Z for requestor in workflow_requestors 2025-12-04T07:56:48.4738446Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T07:56:48.4739088Z ] 2025-12-04T07:56:48.4739296Z 2025-12-04T07:56:48.4739478Z enabled = False 2025-12-04T07:56:48.4739916Z if opted_in_users: 2025-12-04T07:56:48.4740357Z log.info( 2025-12-04T07:56:48.4740954Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T07:56:48.4741653Z ) 2025-12-04T07:56:48.4742035Z enabled = True 2025-12-04T07:56:48.4742321Z 2025-12-04T07:56:48.4742538Z elif experiment_settings.rollout_perc: 2025-12-04T07:56:48.4743535Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T07:56:48.4744499Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T07:56:48.4745165Z log.info( 2025-12-04T07:56:48.4746059Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T07:56:48.4747016Z ) 2025-12-04T07:56:48.4747420Z enabled = True 2025-12-04T07:56:48.4747731Z 2025-12-04T07:56:48.4748001Z if enabled: 2025-12-04T07:56:48.4748421Z label = experiment_name 2025-12-04T07:56:48.4748986Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T07:56:48.4749871Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T07:56:48.4750789Z # - If it's enabled, then we always list it's prefix first 2025-12-04T07:56:48.4751590Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T07:56:48.4752280Z if is_canary: 2025-12-04T07:56:48.4752784Z label += CANARY_FLEET_SUFFIX 2025-12-04T07:56:48.4753346Z fleet_prefix = label 2025-12-04T07:56:48.4753849Z else: 2025-12-04T07:56:48.4754279Z prefixes.append(label) 2025-12-04T07:56:48.4754637Z 2025-12-04T07:56:48.4754821Z if len(prefixes) > 1: 2025-12-04T07:56:48.4755272Z log.error( 2025-12-04T07:56:48.4756356Z 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-04T07:56:48.4757570Z ) 2025-12-04T07:56:48.4758064Z prefixes = prefixes[:1] 2025-12-04T07:56:48.4758394Z 2025-12-04T07:56:48.4758583Z # Fleet always comes first 2025-12-04T07:56:48.4759065Z if fleet_prefix: 2025-12-04T07:56:48.4759514Z prefixes.insert(0, fleet_prefix) 2025-12-04T07:56:48.4759890Z 2025-12-04T07:56:48.4760272Z return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T07:56:48.4760712Z 2025-12-04T07:56:48.4760719Z 2025-12-04T07:56:48.4761177Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T07:56:48.4761991Z """ 2025-12-04T07:56:48.4762586Z Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T07:56:48.4763191Z 2025-12-04T07:56:48.4763590Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T07:56:48.4764328Z """ 2025-12-04T07:56:48.4764709Z gh = get_gh_client(github_token) 2025-12-04T07:56:48.4765266Z issue = get_issue(gh, repo, issue_num) 2025-12-04T07:56:48.4765910Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T07:56:48.4766383Z 2025-12-04T07:56:48.4766390Z 2025-12-04T07:56:48.4766802Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T07:56:48.4767603Z for _ in range(num_retries): 2025-12-04T07:56:48.4768209Z try: 2025-12-04T07:56:48.4768653Z req = Request(url=url, headers=headers) 2025-12-04T07:56:48.4769336Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T07:56:48.4770008Z return json.loads(content) 2025-12-04T07:56:48.4770555Z except Exception as e: 2025-12-04T07:56:48.4771103Z log.warning(f"Could not download {url}: {e}") 2025-12-04T07:56:48.4771524Z 2025-12-04T07:56:48.4771917Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T07:56:48.4772676Z return {} 2025-12-04T07:56:48.4772896Z 2025-12-04T07:56:48.4772903Z 2025-12-04T07:56:48.4773084Z @cache 2025-12-04T07:56:48.4773715Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T07:56:48.4774519Z """ 2025-12-04T07:56:48.4774908Z Dynamically get PR information 2025-12-04T07:56:48.4775560Z """ 2025-12-04T07:56:48.4776071Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T07:56:48.4776738Z headers = { 2025-12-04T07:56:48.4777199Z "Accept": "application/vnd.github.v3+json", 2025-12-04T07:56:48.4777942Z "Authorization": f"token {github_token}", 2025-12-04T07:56:48.4778518Z } 2025-12-04T07:56:48.4778955Z json_response: dict[str, Any] = download_json( 2025-12-04T07:56:48.4779597Z url=f"{github_api}/issues/{pr_number}", 2025-12-04T07:56:48.4780180Z headers=headers, 2025-12-04T07:56:48.4780659Z ) 2025-12-04T07:56:48.4780867Z 2025-12-04T07:56:48.4781057Z if not json_response: 2025-12-04T07:56:48.4781650Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T07:56:48.4782297Z return {} 2025-12-04T07:56:48.4782548Z 2025-12-04T07:56:48.4782735Z return json_response 2025-12-04T07:56:48.4783023Z 2025-12-04T07:56:48.4783029Z 2025-12-04T07:56:48.4783453Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T07:56:48.4784238Z """ 2025-12-04T07:56:48.4784788Z Dynamically get the latest list of labels from the pull request 2025-12-04T07:56:48.4785472Z """ 2025-12-04T07:56:48.4785964Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T07:56:48.4786592Z return { 2025-12-04T07:56:48.4787188Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T07:56:48.4788014Z } 2025-12-04T07:56:48.4788225Z 2025-12-04T07:56:48.4788232Z 2025-12-04T07:56:48.4788430Z def main() -> None: 2025-12-04T07:56:48.4788865Z args = parse_args() 2025-12-04T07:56:48.4789139Z 2025-12-04T07:56:48.4789369Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T07:56:48.4789781Z 2025-12-04T07:56:48.4789974Z # Check if the PR is opt-out 2025-12-04T07:56:48.4790464Z if args.pr_number: 2025-12-04T07:56:48.4791141Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T07:56:48.4792057Z if OPT_OUT_LABEL in labels: 2025-12-04T07:56:48.4792585Z log.info( 2025-12-04T07:56:48.4793310Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T07:56:48.4794101Z ) 2025-12-04T07:56:48.4794680Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T07:56:48.4795368Z sys.exit() 2025-12-04T07:56:48.4795644Z 2025-12-04T07:56:48.4795811Z try: 2025-12-04T07:56:48.4796254Z rollout_state = get_rollout_state_from_issue( 2025-12-04T07:56:48.4796988Z args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T07:56:48.4797665Z ) 2025-12-04T07:56:48.4797986Z 2025-12-04T07:56:48.4798199Z username = get_potential_pr_author( 2025-12-04T07:56:48.4798767Z args.github_token, 2025-12-04T07:56:48.4799254Z args.github_repo, 2025-12-04T07:56:48.4799755Z args.github_actor, 2025-12-04T07:56:48.4800251Z args.github_ref_type, 2025-12-04T07:56:48.4800764Z args.github_branch, 2025-12-04T07:56:48.4801231Z ) 2025-12-04T07:56:48.4801448Z 2025-12-04T07:56:48.4801732Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T07:56:48.4802199Z 2025-12-04T07:56:48.4802421Z runner_label_prefix = get_runner_prefix( 2025-12-04T07:56:48.4802979Z rollout_state, 2025-12-04T07:56:48.4803476Z (args.github_issue_owner, username), 2025-12-04T07:56:48.4804033Z args.github_branch, 2025-12-04T07:56:48.4804535Z args.eligible_experiments, 2025-12-04T07:56:48.4805080Z args.opt_out_experiments, 2025-12-04T07:56:48.4805594Z is_canary, 2025-12-04T07:56:48.4806009Z ) 2025-12-04T07:56:48.4806224Z 2025-12-04T07:56:48.4806410Z except Exception as e: 2025-12-04T07:56:48.4806869Z log.error( 2025-12-04T07:56:48.4807540Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T07:56:48.4808626Z ) 2025-12-04T07:56:48.4808835Z 2025-12-04T07:56:48.4809179Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T07:56:48.4809707Z 2025-12-04T07:56:48.4809714Z 2025-12-04T07:56:48.4809901Z if __name__ == "__main__": 2025-12-04T07:56:48.4810347Z main() 2025-12-04T07:56:48.4810572Z 2025-12-04T07:56:48.4905717Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T07:56:48.4906680Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T07:56:48.4942175Z shell: /usr/bin/bash -e {0} 2025-12-04T07:56:48.4942710Z env: 2025-12-04T07:56:48.4943400Z GITHUB_TOKEN: *** 2025-12-04T07:56:48.4943861Z ISSUE_NUMBER: 5132 2025-12-04T07:56:48.4944360Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T07:56:48.4944925Z ISSUE_OWNER: 2025-12-04T07:56:48.4945373Z CHECK_EXPERIMENTS: 2025-12-04T07:56:48.4945856Z OPT_OUT_EXPERIMENTS: 2025-12-04T07:56:48.4946359Z PR_NUMBER: 2025-12-04T07:56:48.4946793Z ##[endgroup] 2025-12-04T07:56:49.8570484Z Defaulting to user installation because normal site-packages is not writeable 2025-12-04T07:56:51.1985436Z Collecting urllib3==1.26.18 2025-12-04T07:56:51.2339967Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-12-04T07:56:51.2702576Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.4 MB/s eta 0:00:00 2025-12-04T07:56:51.2825897Z Collecting PyGithub==2.3.0 2025-12-04T07:56:51.2864525Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-12-04T07:56:51.3328413Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-12-04T07:56:51.3368482Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.8 kB) 2025-12-04T07:56:51.3421289Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-12-04T07:56:51.3438441Z 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-04T07:56:51.3453611Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-12-04T07:56:51.3713775Z Collecting Deprecated (from PyGithub==2.3.0) 2025-12-04T07:56:51.3753054Z Downloading deprecated-1.3.1-py2.py3-none-any.whl.metadata (5.9 kB) 2025-12-04T07:56:51.3978984Z 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-04T07:56:51.5172221Z Collecting cffi>=2.0.0 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T07:56:51.5212539Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-12-04T07:56:51.6835068Z Collecting wrapt<3,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-12-04T07:56:51.6879431Z 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-04T07:56:51.7089854Z Collecting pycparser (from cffi>=2.0.0->pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T07:56:51.7129262Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-12-04T07:56:51.7366220Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-12-04T07:56:51.7432650Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 30.6 MB/s eta 0:00:00 2025-12-04T07:56:51.7510044Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-12-04T07:56:51.7568004Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 85.7 MB/s eta 0:00:00 2025-12-04T07:56:51.7606800Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-12-04T07:56:51.7725232Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 137.3 MB/s eta 0:00:00 2025-12-04T07:56:51.7771456Z Downloading deprecated-1.3.1-py2.py3-none-any.whl (11 kB) 2025-12-04T07:56:51.7830982Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-12-04T07:56:51.7885123Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 56.4 MB/s eta 0:00:00 2025-12-04T07:56:51.7922203Z 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-04T07:56:51.7966645Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.5/121.5 kB 40.7 MB/s eta 0:00:00 2025-12-04T07:56:51.8002441Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-12-04T07:56:51.8058858Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 26.9 MB/s eta 0:00:00 2025-12-04T07:56:52.1144670Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-12-04T07:56:52.6734013Z 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-04T07:56:52.7806329Z ##[group]Run curr_branch="main" 2025-12-04T07:56:52.7806656Z curr_branch="main" 2025-12-04T07:56:52.7806908Z curr_ref_type="branch" 2025-12-04T07:56:52.7807189Z echo "Current branch is '$curr_branch'" 2025-12-04T07:56:52.7807521Z  2025-12-04T07:56:52.7807727Z python3 runner_determinator.py \ 2025-12-04T07:56:52.7808221Z  --github-token "$GITHUB_TOKEN" \ 2025-12-04T07:56:52.7808537Z  --github-issue "$ISSUE_NUMBER" \ 2025-12-04T07:56:52.7808826Z  --github-branch "$curr_branch" \ 2025-12-04T07:56:52.7809138Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-12-04T07:56:52.7809464Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-12-04T07:56:52.7809794Z  --github-ref-type "$curr_ref_type" \ 2025-12-04T07:56:52.7810102Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-12-04T07:56:52.7810453Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-12-04T07:56:52.7810875Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-12-04T07:56:52.7811200Z  --pr-number "${PR_NUMBER}" 2025-12-04T07:56:52.7846787Z shell: /usr/bin/bash -e {0} 2025-12-04T07:56:52.7847062Z env: 2025-12-04T07:56:52.7847932Z GITHUB_TOKEN: *** 2025-12-04T07:56:52.7848184Z ISSUE_NUMBER: 5132 2025-12-04T07:56:52.7848439Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T07:56:52.7848719Z ISSUE_OWNER: 2025-12-04T07:56:52.7848927Z CHECK_EXPERIMENTS: 2025-12-04T07:56:52.7849165Z OPT_OUT_EXPERIMENTS: 2025-12-04T07:56:52.7849382Z PR_NUMBER: 2025-12-04T07:56:52.7849584Z ##[endgroup] 2025-12-04T07:56:52.7903758Z Current branch is 'main' 2025-12-04T07:56:54.3246750Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-12-04T07:56:54.3248220Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-12-04T07:56:54.3249681Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-12-04T07:56:54.3250410Z INFO : Setting output: label-type='' 2025-12-04T07:56:54.3673081Z Evaluate and set job outputs 2025-12-04T07:56:54.3681322Z Cleaning up orphan processes