2025-12-04T08:41:21.3503178Z Current runner version: '2.329.0' 2025-12-04T08:41:21.3525044Z ##[group]Runner Image Provisioner 2025-12-04T08:41:21.3525990Z Hosted Compute Agent 2025-12-04T08:41:21.3526585Z Version: 20251124.448 2025-12-04T08:41:21.3527247Z Commit: fda5086b43ec66ade217e5fcd18146c879571177 2025-12-04T08:41:21.3528243Z Build Date: 2025-11-24T21:16:26Z 2025-12-04T08:41:21.3528913Z ##[endgroup] 2025-12-04T08:41:21.3529510Z ##[group]Operating System 2025-12-04T08:41:21.3530089Z Ubuntu 2025-12-04T08:41:21.3530705Z 24.04.3 2025-12-04T08:41:21.3531204Z LTS 2025-12-04T08:41:21.3531752Z ##[endgroup] 2025-12-04T08:41:21.3532340Z ##[group]Runner Image 2025-12-04T08:41:21.3532978Z Image: ubuntu-24.04 2025-12-04T08:41:21.3533540Z Version: 20251126.144.1 2025-12-04T08:41:21.3534699Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251126.144/images/ubuntu/Ubuntu2404-Readme.md 2025-12-04T08:41:21.3536321Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251126.144 2025-12-04T08:41:21.3537458Z ##[endgroup] 2025-12-04T08:41:21.3538720Z ##[group]GITHUB_TOKEN Permissions 2025-12-04T08:41:21.3541030Z Contents: read 2025-12-04T08:41:21.3541642Z Metadata: read 2025-12-04T08:41:21.3542306Z ##[endgroup] 2025-12-04T08:41:21.3544375Z Secret source: Actions 2025-12-04T08:41:21.3545162Z Prepare workflow directory 2025-12-04T08:41:21.4041625Z Prepare all required actions 2025-12-04T08:41:21.4097622Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (ffd9b0fb4355e97af82fc42cf185c3ffa0fc0a32) 2025-12-04T08:41:21.4102916Z ##[group] Inputs 2025-12-04T08:41:21.4103682Z check_experiments: 2025-12-04T08:41:21.4104319Z opt_out_experiments: 2025-12-04T08:41:21.4104967Z triggering_actor: pytorchmergebot 2025-12-04T08:41:21.4105649Z issue_owner: 2025-12-04T08:41:21.4106251Z curr_branch: main 2025-12-04T08:41:21.4106822Z curr_ref_type: branch 2025-12-04T08:41:21.4107570Z issue_number: 5132 2025-12-04T08:41:21.4108524Z ##[endgroup] 2025-12-04T08:41:21.4109259Z Complete job name: before-test / get-label-type / runner-determinator 2025-12-04T08:41:22.1297739Z ##[group]Run cat < runner_determinator.py 2025-12-04T08:41:22.1300511Z cat < runner_determinator.py 2025-12-04T08:41:22.1301229Z # flake8: noqa: G004 2025-12-04T08:41:22.1301789Z  2025-12-04T08:41:22.1302506Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T08:41:22.1303624Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T08:41:22.1304597Z # python .github/scripts/update_runner_determinator.py 2025-12-04T08:41:22.1305304Z  2025-12-04T08:41:22.1305807Z """ 2025-12-04T08:41:22.1306487Z This runner determinator is used to determine which set of runners to run a 2025-12-04T08:41:22.1307495Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T08:41:22.1308838Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T08:41:22.1309863Z of which runners should be used to run which job. 2025-12-04T08:41:22.1310568Z  2025-12-04T08:41:22.1311249Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T08:41:22.1312272Z separated by a line containing "---". If the line is not present, the 2025-12-04T08:41:22.1313244Z settings are considered to be empty with only the second part, the user 2025-12-04T08:41:22.1314163Z list, defined. 2025-12-04T08:41:22.1314686Z  2025-12-04T08:41:22.1315355Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T08:41:22.1316493Z used to define any settings that are needed to determine which runners to use. 2025-12-04T08:41:22.1317462Z It's fields are defined by the RolloutSettings class below. 2025-12-04T08:41:22.1318280Z  2025-12-04T08:41:22.1319310Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T08:41:22.1320327Z The user list is also a comma separated list of additional features or 2025-12-04T08:41:22.1321206Z experiments which the user could be opted in to. 2025-12-04T08:41:22.1321943Z  2025-12-04T08:41:22.1322451Z The user list has the following rules: 2025-12-04T08:41:22.1323031Z  2025-12-04T08:41:22.1323733Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T08:41:22.1324690Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T08:41:22.1325544Z - A "#" prefix opts the user out of all experiments 2025-12-04T08:41:22.1326255Z  2025-12-04T08:41:22.1326692Z Example config: 2025-12-04T08:41:22.1327331Z  # A list of experiments that can be opted into. 2025-12-04T08:41:22.1328238Z  # This defines the behavior they'll induce when opted into. 2025-12-04T08:41:22.1329007Z  # Expected syntax is: 2025-12-04T08:41:22.1329790Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T08:41:22.1330890Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T08:41:22.1331789Z  2025-12-04T08:41:22.1332233Z  experiments: 2025-12-04T08:41:22.1332845Z  lf: 2025-12-04T08:41:22.1333382Z  rollout_percent: 25 2025-12-04T08:41:22.1333945Z  all_branches: false 2025-12-04T08:41:22.1334575Z  default: true 2025-12-04T08:41:22.1335123Z  --- 2025-12-04T08:41:22.1335629Z  2025-12-04T08:41:22.1336038Z  # Opt-ins: 2025-12-04T08:41:22.1336843Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T08:41:22.1338175Z  # and specifying experiments to enable in a comma-separated list. 2025-12-04T08:41:22.1339118Z  # To always opt out of an experiment, prefix it with a "-". 2025-12-04T08:41:22.1340021Z  # Experiments should be from the above list. 2025-12-04T08:41:22.1340711Z  2025-12-04T08:41:22.1341214Z  @User1,-lf,split_build 2025-12-04T08:41:22.1341865Z  @User2,lf 2025-12-04T08:41:22.1342444Z  @User3,split_build 2025-12-04T08:41:22.1343034Z """ 2025-12-04T08:41:22.1343533Z  2025-12-04T08:41:22.1344044Z import json 2025-12-04T08:41:22.1344554Z import logging 2025-12-04T08:41:22.1419111Z import os 2025-12-04T08:41:22.1419937Z import random 2025-12-04T08:41:22.1420648Z import re 2025-12-04T08:41:22.1421319Z import sys 2025-12-04T08:41:22.1422106Z from argparse import ArgumentParser 2025-12-04T08:41:22.1423159Z from collections.abc import Iterable 2025-12-04T08:41:22.1423959Z from functools import cache 2025-12-04T08:41:22.1424494Z from logging import LogRecord 2025-12-04T08:41:22.1425039Z from typing import Any, NamedTuple 2025-12-04T08:41:22.1425653Z from urllib.request import Request, urlopen 2025-12-04T08:41:22.1426227Z  2025-12-04T08:41:22.1426597Z import yaml 2025-12-04T08:41:22.1427073Z from github import Auth, Github 2025-12-04T08:41:22.1427619Z from github.Issue import Issue 2025-12-04T08:41:22.1428493Z  2025-12-04T08:41:22.1428864Z  2025-12-04T08:41:22.1429337Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T08:41:22.1430095Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T08:41:22.1430996Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T08:41:22.1431722Z  2025-12-04T08:41:22.1432419Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T08:41:22.1433022Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T08:41:22.1433580Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T08:41:22.1434220Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T08:41:22.1434794Z  2025-12-04T08:41:22.1435239Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T08:41:22.1435780Z  2025-12-04T08:41:22.1436189Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T08:41:22.1436719Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T08:41:22.1437226Z  2025-12-04T08:41:22.1437585Z  2025-12-04T08:41:22.1438179Z class Experiment(NamedTuple): 2025-12-04T08:41:22.1438710Z  rollout_perc: float = ( 2025-12-04T08:41:22.1439406Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T08:41:22.1440104Z  ) 2025-12-04T08:41:22.1440505Z  all_branches: bool = ( 2025-12-04T08:41:22.1441190Z  False # If True, the experiment is also enabled on the exception branches 2025-12-04T08:41:22.1441881Z  ) 2025-12-04T08:41:22.1442273Z  default: bool = ( 2025-12-04T08:41:22.1442892Z  True # If True, the experiment is enabled by default for all queries 2025-12-04T08:41:22.1443550Z  ) 2025-12-04T08:41:22.1443944Z  2025-12-04T08:41:22.1444339Z  # Add more fields as needed 2025-12-04T08:41:22.1444823Z  2025-12-04T08:41:22.1445185Z  2025-12-04T08:41:22.1445613Z class Settings(NamedTuple): 2025-12-04T08:41:22.1446125Z  """ 2025-12-04T08:41:22.1446639Z  Settings for the experiments that can be opted into. 2025-12-04T08:41:22.1447229Z  """ 2025-12-04T08:41:22.1447615Z  2025-12-04T08:41:22.1448215Z  experiments: dict[str, Experiment] = {} 2025-12-04T08:41:22.1448755Z  2025-12-04T08:41:22.1449241Z  2025-12-04T08:41:22.1449686Z class ColorFormatter(logging.Formatter): 2025-12-04T08:41:22.1450384Z  """Color codes the log messages based on the log level""" 2025-12-04T08:41:22.1450983Z  2025-12-04T08:41:22.1451353Z  COLORS = { 2025-12-04T08:41:22.1451839Z  "WARNING": "\033[33m", # Yellow 2025-12-04T08:41:22.1452416Z  "ERROR": "\033[31m", # Red 2025-12-04T08:41:22.1452971Z  "CRITICAL": "\033[31m", # Red 2025-12-04T08:41:22.1453534Z  "INFO": "\033[0m", # Reset 2025-12-04T08:41:22.1454070Z  "DEBUG": "\033[0m", # Reset 2025-12-04T08:41:22.1454582Z  } 2025-12-04T08:41:22.1454984Z  2025-12-04T08:41:22.1455428Z  def format(self, record: LogRecord) -> str: 2025-12-04T08:41:22.1456232Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T08:41:22.1457060Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T08:41:22.1457692Z  return super().format(record) 2025-12-04T08:41:22.1458433Z  2025-12-04T08:41:22.1458796Z  2025-12-04T08:41:22.1459192Z handler = logging.StreamHandler() 2025-12-04T08:41:22.1459966Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T08:41:22.1460702Z  2025-12-04T08:41:22.1461176Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T08:41:22.1461788Z log.addHandler(handler) 2025-12-04T08:41:22.1462311Z log.setLevel(logging.INFO) 2025-12-04T08:41:22.1462788Z  2025-12-04T08:41:22.1463139Z  2025-12-04T08:41:22.1463643Z def set_github_output(key: str, value: str) -> None: 2025-12-04T08:41:22.1464233Z  """ 2025-12-04T08:41:22.1464780Z  Defines outputs of the github action that invokes this script 2025-12-04T08:41:22.1465570Z  """ 2025-12-04T08:41:22.1466002Z  if not GITHUB_OUTPUT: 2025-12-04T08:41:22.1467126Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T08:41:22.1468443Z  log.warning( 2025-12-04T08:41:22.1469325Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T08:41:22.1470241Z  ) 2025-12-04T08:41:22.1470707Z  print(f"::set-output name={key}::{value}") 2025-12-04T08:41:22.1471266Z  return 2025-12-04T08:41:22.1471679Z  2025-12-04T08:41:22.1472089Z  with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T08:41:22.1472689Z  log.info(f"Setting output: {key}='{value}'") 2025-12-04T08:41:22.1473294Z  f.write(f"{key}={value}\n") 2025-12-04T08:41:22.1473821Z  2025-12-04T08:41:22.1474201Z  2025-12-04T08:41:22.1474751Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T08:41:22.1475438Z  return frozenset( 2025-12-04T08:41:22.1476124Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T08:41:22.1476836Z  ) 2025-12-04T08:41:22.1477218Z  2025-12-04T08:41:22.1477583Z  2025-12-04T08:41:22.1478139Z def parse_args() -> Any: 2025-12-04T08:41:22.1478776Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T08:41:22.1479647Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T08:41:22.1480404Z  parser.add_argument( 2025-12-04T08:41:22.1480894Z  "--github-issue-repo", 2025-12-04T08:41:22.1481425Z  type=str, 2025-12-04T08:41:22.1481904Z  required=False, 2025-12-04T08:41:22.1482571Z  default="pytorch/test-infra", 2025-12-04T08:41:22.1483171Z  help="GitHub repo to get the issue", 2025-12-04T08:41:22.1483699Z  ) 2025-12-04T08:41:22.1484101Z  parser.add_argument( 2025-12-04T08:41:22.1484584Z  "--github-repo", 2025-12-04T08:41:22.1485061Z  type=str, 2025-12-04T08:41:22.1485503Z  required=True, 2025-12-04T08:41:22.1486020Z  help="GitHub repo where CI is running", 2025-12-04T08:41:22.1486577Z  ) 2025-12-04T08:41:22.1486973Z  parser.add_argument( 2025-12-04T08:41:22.1487625Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T08:41:22.1488461Z  ) 2025-12-04T08:41:22.1488870Z  parser.add_argument( 2025-12-04T08:41:22.1489537Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T08:41:22.1490236Z  ) 2025-12-04T08:41:22.1490628Z  parser.add_argument( 2025-12-04T08:41:22.1491311Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T08:41:22.1491996Z  ) 2025-12-04T08:41:22.1492391Z  parser.add_argument( 2025-12-04T08:41:22.1493115Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T08:41:22.1493826Z  ) 2025-12-04T08:41:22.1494260Z  parser.add_argument( 2025-12-04T08:41:22.1494786Z  "--github-ref-type", 2025-12-04T08:41:22.1495316Z  type=str, 2025-12-04T08:41:22.1495775Z  required=True, 2025-12-04T08:41:22.1496343Z  help="Current GitHub ref type, branch or tag", 2025-12-04T08:41:22.1496911Z  ) 2025-12-04T08:41:22.1497324Z  parser.add_argument( 2025-12-04T08:41:22.1498194Z  "--eligible-experiments", 2025-12-04T08:41:22.1498812Z  type=_str_comma_separated_to_set, 2025-12-04T08:41:22.1499388Z  required=False, 2025-12-04T08:41:22.1499884Z  default="", 2025-12-04T08:41:22.1500796Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T08:41:22.1501736Z  ) 2025-12-04T08:41:22.1502166Z  parser.add_argument( 2025-12-04T08:41:22.1502683Z  "--opt-out-experiments", 2025-12-04T08:41:22.1503239Z  type=_str_comma_separated_to_set, 2025-12-04T08:41:22.1503806Z  required=False, 2025-12-04T08:41:22.1504313Z  default="", 2025-12-04T08:41:22.1504776Z  help=( 2025-12-04T08:41:22.1505486Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T08:41:22.1506610Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T08:41:22.1507470Z  ), 2025-12-04T08:41:22.1508238Z  ) 2025-12-04T08:41:22.1508657Z  parser.add_argument( 2025-12-04T08:41:22.1509152Z  "--pr-number", 2025-12-04T08:41:22.1509645Z  type=str, 2025-12-04T08:41:22.1510124Z  required=False, 2025-12-04T08:41:22.1510632Z  default="", 2025-12-04T08:41:22.1511193Z  help="the optional PR number where this is run", 2025-12-04T08:41:22.1511796Z  ) 2025-12-04T08:41:22.1512205Z  2025-12-04T08:41:22.1512604Z  return parser.parse_args() 2025-12-04T08:41:22.1513114Z  2025-12-04T08:41:22.1513481Z  2025-12-04T08:41:22.1514133Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T08:41:22.1515088Z  auth = Auth.Token(github_token) 2025-12-04T08:41:22.1515673Z  return Github(auth=auth) 2025-12-04T08:41:22.1516153Z  2025-12-04T08:41:22.1516500Z  2025-12-04T08:41:22.1517157Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T08:41:22.1518141Z  repo = gh.get_repo(repo) 2025-12-04T08:41:22.1518696Z  return repo.get_issue(number=issue_num) 2025-12-04T08:41:22.1519225Z  2025-12-04T08:41:22.1519592Z  2025-12-04T08:41:22.1520000Z def get_potential_pr_author( 2025-12-04T08:41:22.1520681Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T08:41:22.1521362Z ) -> str: 2025-12-04T08:41:22.1521947Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T08:41:22.1522797Z  # Fetch the actual username from the original PR. The PR number is 2025-12-04T08:41:22.1523610Z  # embedded in the tag name: ciflow// 2025-12-04T08:41:22.1524220Z  2025-12-04T08:41:22.1524612Z  gh = get_gh_client(github_token) 2025-12-04T08:41:22.1525117Z  2025-12-04T08:41:22.1525598Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T08:41:22.1526254Z  split_tag = ref_name.split("/") 2025-12-04T08:41:22.1526797Z  if ( 2025-12-04T08:41:22.1527254Z  len(split_tag) == 3 2025-12-04T08:41:22.1527790Z  and split_tag[0] == "ciflow" 2025-12-04T08:41:22.1528522Z  and split_tag[2].isnumeric() 2025-12-04T08:41:22.1529028Z  ): 2025-12-04T08:41:22.1529486Z  pr_number = split_tag[2] 2025-12-04T08:41:22.1530033Z  try: 2025-12-04T08:41:22.1530520Z  repository = gh.get_repo(repo) 2025-12-04T08:41:22.1531293Z  pull = repository.get_pull(number=int(pr_number)) 2025-12-04T08:41:22.1531921Z  except Exception as e: 2025-12-04T08:41:22.1532508Z  raise Exception( # noqa: TRY002 2025-12-04T08:41:22.1533201Z  f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T08:41:22.1533855Z  ) from e 2025-12-04T08:41:22.1534469Z  return pull.user.login # type: ignore[no-any-return] 2025-12-04T08:41:22.1535223Z  # In all other cases, return the original input username 2025-12-04T08:41:22.1535850Z  return username 2025-12-04T08:41:22.1536321Z  2025-12-04T08:41:22.1536667Z  2025-12-04T08:41:22.1537101Z def is_exception_branch(branch: str) -> bool: 2025-12-04T08:41:22.1537674Z  """ 2025-12-04T08:41:22.1538584Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T08:41:22.1539370Z  """ 2025-12-04T08:41:22.1539985Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T08:41:22.1540685Z  2025-12-04T08:41:22.1541067Z  2025-12-04T08:41:22.1541510Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T08:41:22.1542052Z  try: 2025-12-04T08:41:22.1542505Z  data = yaml.safe_load(yaml_text) 2025-12-04T08:41:22.1543027Z  return data 2025-12-04T08:41:22.1543489Z  except yaml.YAMLError: 2025-12-04T08:41:22.1544012Z  log.exception("Error loading YAML") 2025-12-04T08:41:22.1544549Z  raise 2025-12-04T08:41:22.1544981Z  2025-12-04T08:41:22.1545364Z  2025-12-04T08:41:22.1546015Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T08:41:22.1546786Z  """ 2025-12-04T08:41:22.1547564Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T08:41:22.1548520Z  2025-12-04T08:41:22.1549081Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T08:41:22.1549862Z  and the text below is the list of opted in users. 2025-12-04T08:41:22.1550422Z  2025-12-04T08:41:22.1551028Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T08:41:22.1551744Z  """ 2025-12-04T08:41:22.1552259Z  rollout_state_parts = rollout_state.split("---") 2025-12-04T08:41:22.1552902Z  if len(rollout_state_parts) >= 2: 2025-12-04T08:41:22.1553573Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T08:41:22.1554211Z  else: 2025-12-04T08:41:22.1554646Z  return "", rollout_state 2025-12-04T08:41:22.1555132Z  2025-12-04T08:41:22.1555485Z  2025-12-04T08:41:22.1556162Z class UserOptins(dict[str, list[str]]): 2025-12-04T08:41:22.1556704Z  """ 2025-12-04T08:41:22.1557272Z  Dictionary of users with a list of features they have opted into 2025-12-04T08:41:22.1558105Z  """ 2025-12-04T08:41:22.1558484Z  2025-12-04T08:41:22.1558832Z  2025-12-04T08:41:22.1559403Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T08:41:22.1560096Z  """ 2025-12-04T08:41:22.1560820Z  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:41:22.1561644Z  2025-12-04T08:41:22.1562824Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T08:41:22.1563825Z  - Example line: "@User1,lf,split_build" 2025-12-04T08:41:22.1564725Z  - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T08:41:22.1565391Z  2025-12-04T08:41:22.1565736Z  2025-12-04T08:41:22.1566082Z  """ 2025-12-04T08:41:22.1566485Z  optins = UserOptins() 2025-12-04T08:41:22.1567042Z  for user in user_optin_text.split("\n"): 2025-12-04T08:41:22.1567662Z  user = user.strip("\r\n\t -") 2025-12-04T08:41:22.1568470Z  if not user or not user.startswith("@"): 2025-12-04T08:41:22.1569072Z  # Not a valid user. Skip 2025-12-04T08:41:22.1569584Z  continue 2025-12-04T08:41:22.1570008Z  2025-12-04T08:41:22.1570358Z  if user: 2025-12-04T08:41:22.1570837Z  usr_name = user.split(",")[0].strip("@") 2025-12-04T08:41:22.1571564Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T08:41:22.1572279Z  2025-12-04T08:41:22.1572678Z  return optins 2025-12-04T08:41:22.1573143Z  2025-12-04T08:41:22.1573514Z  2025-12-04T08:41:22.1574015Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T08:41:22.1574628Z  """ 2025-12-04T08:41:22.1575086Z  Check if the experiment name is valid. 2025-12-04T08:41:22.1575651Z  A valid name: 2025-12-04T08:41:22.1576363Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T08:41:22.1577322Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T08:41:22.1578255Z  - Cannot contain spaces 2025-12-04T08:41:22.1578752Z  """ 2025-12-04T08:41:22.1579158Z  2025-12-04T08:41:22.1579665Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T08:41:22.1580403Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T08:41:22.1581168Z  2025-12-04T08:41:22.1581562Z  if valid: 2025-12-04T08:41:22.1582007Z  return True 2025-12-04T08:41:22.1582485Z  2025-12-04T08:41:22.1582834Z  log.error( 2025-12-04T08:41:22.1584246Z  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:41:22.1585737Z  ) 2025-12-04T08:41:22.1586124Z  return False 2025-12-04T08:41:22.1586572Z  2025-12-04T08:41:22.1586937Z  2025-12-04T08:41:22.1587494Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T08:41:22.1588719Z  """ 2025-12-04T08:41:22.1589374Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T08:41:22.1590119Z  """ 2025-12-04T08:41:22.1590493Z  try: 2025-12-04T08:41:22.1590896Z  if settings_text: 2025-12-04T08:41:22.1591675Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T08:41:22.1592492Z  # for easy reading 2025-12-04T08:41:22.1593338Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T08:41:22.1594270Z  # the backtick character in shell commands. 2025-12-04T08:41:22.1594924Z  backtick = chr(96) # backtick character 2025-12-04T08:41:22.1595609Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T08:41:22.1596307Z  settings = load_yaml(settings_text) 2025-12-04T08:41:22.1596841Z  2025-12-04T08:41:22.1597439Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T08:41:22.1598460Z  experiments = {} 2025-12-04T08:41:22.1598938Z  2025-12-04T08:41:22.1599501Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T08:41:22.1600292Z  if not is_valid_experiment_name(exp_name): 2025-12-04T08:41:22.1601404Z  # 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:41:22.1602444Z  continue 2025-12-04T08:41:22.1602925Z  2025-12-04T08:41:22.1603305Z  valid_settings = {} 2025-12-04T08:41:22.1603853Z  for setting in exp_settings: 2025-12-04T08:41:22.1604484Z  if setting not in Experiment._fields: 2025-12-04T08:41:22.1605053Z  log.warning( 2025-12-04T08:41:22.1605801Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T08:41:22.1606536Z  ) 2025-12-04T08:41:22.1607019Z  else: 2025-12-04T08:41:22.1607590Z  valid_settings[setting] = exp_settings[setting] 2025-12-04T08:41:22.1608285Z  2025-12-04T08:41:22.1608774Z  experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T08:41:22.1609437Z  return Settings(experiments) 2025-12-04T08:41:22.1609953Z  2025-12-04T08:41:22.1610317Z  except Exception: 2025-12-04T08:41:22.1610846Z  log.exception("Failed to parse settings") 2025-12-04T08:41:22.1611381Z  2025-12-04T08:41:22.1611764Z  return Settings() 2025-12-04T08:41:22.1612223Z  2025-12-04T08:41:22.1612572Z  2025-12-04T08:41:22.1613143Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T08:41:22.1613722Z  """ 2025-12-04T08:41:22.1614220Z  Parse settings, if any, from the rollout state. 2025-12-04T08:41:22.1614804Z  2025-12-04T08:41:22.1615372Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T08:41:22.1616168Z  and the text below is the list of opted in users. 2025-12-04T08:41:22.1616749Z  2025-12-04T08:41:22.1617351Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T08:41:22.1618189Z  """ 2025-12-04T08:41:22.1618776Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:41:22.1619565Z  return parse_settings_from_text(settings_text) 2025-12-04T08:41:22.1620109Z  2025-12-04T08:41:22.1620446Z  2025-12-04T08:41:22.1620910Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T08:41:22.1621480Z  """ 2025-12-04T08:41:22.1621919Z  Parse users from the rollout state. 2025-12-04T08:41:22.1622445Z  2025-12-04T08:41:22.1622821Z  """ 2025-12-04T08:41:22.1623381Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:41:22.1624115Z  return parse_user_opt_in_from_text(users_text) 2025-12-04T08:41:22.1624692Z  2025-12-04T08:41:22.1625044Z  2025-12-04T08:41:22.1625667Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:41:22.1626408Z  """ 2025-12-04T08:41:22.1626864Z  Check if a user is opted into an experiment 2025-12-04T08:41:22.1627416Z  """ 2025-12-04T08:41:22.1628022Z  return experiment_name in user_optins.get(user, []) 2025-12-04T08:41:22.1628829Z  2025-12-04T08:41:22.1629205Z  2025-12-04T08:41:22.1629870Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:41:22.1630651Z  """ 2025-12-04T08:41:22.1631136Z  Check if a user explicitly opted out of an experiment 2025-12-04T08:41:22.1631737Z  """ 2025-12-04T08:41:22.1632292Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T08:41:22.1632994Z  experiment_optout = "-" + experiment_name 2025-12-04T08:41:22.1633650Z  if experiment_optout not in user_optins.get(user, []): 2025-12-04T08:41:22.1634246Z  return False 2025-12-04T08:41:22.1634676Z  2025-12-04T08:41:22.1635142Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T08:41:22.1635785Z  log.warning( 2025-12-04T08:41:22.1636612Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T08:41:22.1637476Z  ) 2025-12-04T08:41:22.1637955Z  2025-12-04T08:41:22.1638316Z  return True 2025-12-04T08:41:22.1638737Z  2025-12-04T08:41:22.1639079Z  2025-12-04T08:41:22.1639448Z def get_runner_prefix( 2025-12-04T08:41:22.1639947Z  rollout_state: str, 2025-12-04T08:41:22.1640477Z  workflow_requestors: Iterable[str], 2025-12-04T08:41:22.1640996Z  branch: str, 2025-12-04T08:41:22.1641541Z  eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T08:41:22.1642243Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T08:41:22.1642852Z  is_canary: bool = False, 2025-12-04T08:41:22.1643337Z ) -> str: 2025-12-04T08:41:22.1643804Z  settings = parse_settings(rollout_state) 2025-12-04T08:41:22.1644438Z  user_optins = parse_users(rollout_state) 2025-12-04T08:41:22.1644994Z  2025-12-04T08:41:22.1645489Z  fleet_prefix = "" 2025-12-04T08:41:22.1645968Z  prefixes = [] 2025-12-04T08:41:22.1646661Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T08:41:22.1647629Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T08:41:22.1648472Z  log.info( 2025-12-04T08:41:22.1649210Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T08:41:22.1649946Z  ) 2025-12-04T08:41:22.1650398Z  continue 2025-12-04T08:41:22.1650859Z  2025-12-04T08:41:22.1651247Z  if opt_out_experiments: 2025-12-04T08:41:22.1651811Z  if experiment_name in opt_out_experiments: 2025-12-04T08:41:22.1652451Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T08:41:22.1653052Z  log.info( 2025-12-04T08:41:22.1653978Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T08:41:22.1654939Z  ) 2025-12-04T08:41:22.1655374Z  continue 2025-12-04T08:41:22.1655829Z  2025-12-04T08:41:22.1656215Z  if eligible_experiments: 2025-12-04T08:41:22.1656836Z  if experiment_name not in eligible_experiments: 2025-12-04T08:41:22.1657509Z  exp_list = ", ".join(eligible_experiments) 2025-12-04T08:41:22.1658187Z  log.info( 2025-12-04T08:41:22.1659012Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T08:41:22.1659865Z  ) 2025-12-04T08:41:22.1660424Z  continue 2025-12-04T08:41:22.1660961Z  elif not experiment_settings.default: 2025-12-04T08:41:22.1661512Z  log.info( 2025-12-04T08:41:22.1662228Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T08:41:22.1662941Z  ) 2025-12-04T08:41:22.1663375Z  continue 2025-12-04T08:41:22.1663805Z  2025-12-04T08:41:22.1664292Z  # Is any workflow_requestor opted out to this experiment? 2025-12-04T08:41:22.1664918Z  opted_out_users = [ 2025-12-04T08:41:22.1665389Z  requestor 2025-12-04T08:41:22.1665895Z  for requestor in workflow_requestors 2025-12-04T08:41:22.1666570Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T08:41:22.1667193Z  ] 2025-12-04T08:41:22.1667576Z  2025-12-04T08:41:22.1668040Z  if opted_out_users: 2025-12-04T08:41:22.1668525Z  log.info( 2025-12-04T08:41:22.1669174Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T08:41:22.1669891Z  ) 2025-12-04T08:41:22.1670306Z  continue 2025-12-04T08:41:22.1670735Z  2025-12-04T08:41:22.1671234Z  # Is any workflow_requestor opted in to this experiment? 2025-12-04T08:41:22.1671866Z  opted_in_users = [ 2025-12-04T08:41:22.1672360Z  requestor 2025-12-04T08:41:22.1672866Z  for requestor in workflow_requestors 2025-12-04T08:41:22.1673567Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T08:41:22.1674208Z  ] 2025-12-04T08:41:22.1674602Z  2025-12-04T08:41:22.1674962Z  enabled = False 2025-12-04T08:41:22.1675445Z  if opted_in_users: 2025-12-04T08:41:22.1676056Z  log.info( 2025-12-04T08:41:22.1676692Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T08:41:22.1677405Z  ) 2025-12-04T08:41:22.1677945Z  enabled = True 2025-12-04T08:41:22.1678435Z  2025-12-04T08:41:22.1678884Z  elif experiment_settings.rollout_perc: 2025-12-04T08:41:22.1679708Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T08:41:22.1680623Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T08:41:22.1681302Z  log.info( 2025-12-04T08:41:22.1682192Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T08:41:22.1683097Z  ) 2025-12-04T08:41:22.1683588Z  enabled = True 2025-12-04T08:41:22.1684098Z  2025-12-04T08:41:22.1684498Z  if enabled: 2025-12-04T08:41:22.1685000Z  label = experiment_name 2025-12-04T08:41:22.1685594Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T08:41:22.1686420Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T08:41:22.1687303Z  # - If it's enabled, then we always list it's prefix first 2025-12-04T08:41:22.1688200Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T08:41:22.1688896Z  if is_canary: 2025-12-04T08:41:22.1689445Z  label += CANARY_FLEET_SUFFIX 2025-12-04T08:41:22.1690037Z  fleet_prefix = label 2025-12-04T08:41:22.1690539Z  else: 2025-12-04T08:41:22.1691163Z  prefixes.append(label) 2025-12-04T08:41:22.1691665Z  2025-12-04T08:41:22.1692052Z  if len(prefixes) > 1: 2025-12-04T08:41:22.1692530Z  log.error( 2025-12-04T08:41:22.1693553Z  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:41:22.1694646Z  ) 2025-12-04T08:41:22.1695091Z  prefixes = prefixes[:1] 2025-12-04T08:41:22.1695588Z  2025-12-04T08:41:22.1695968Z  # Fleet always comes first 2025-12-04T08:41:22.1696480Z  if fleet_prefix: 2025-12-04T08:41:22.1697003Z  prefixes.insert(0, fleet_prefix) 2025-12-04T08:41:22.1697539Z  2025-12-04T08:41:22.1698101Z  return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T08:41:22.1698753Z  2025-12-04T08:41:22.1699118Z  2025-12-04T08:41:22.1699750Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T08:41:22.1700532Z  """ 2025-12-04T08:41:22.1701172Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T08:41:22.1701895Z  2025-12-04T08:41:22.1702474Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T08:41:22.1703193Z  """ 2025-12-04T08:41:22.1703630Z  gh = get_gh_client(github_token) 2025-12-04T08:41:22.1704197Z  issue = get_issue(gh, repo, issue_num) 2025-12-04T08:41:22.1704876Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T08:41:22.1705480Z  2025-12-04T08:41:22.1705836Z  2025-12-04T08:41:22.1706430Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T08:41:22.1707293Z  for _ in range(num_retries): 2025-12-04T08:41:22.1707796Z  try: 2025-12-04T08:41:22.1708423Z  req = Request(url=url, headers=headers) 2025-12-04T08:41:22.1709127Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T08:41:22.1709795Z  return json.loads(content) 2025-12-04T08:41:22.1710364Z  except Exception as e: 2025-12-04T08:41:22.1710973Z  log.warning(f"Could not download {url}: {e}") 2025-12-04T08:41:22.1711555Z  2025-12-04T08:41:22.1712134Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T08:41:22.1712857Z  return {} 2025-12-04T08:41:22.1713286Z  2025-12-04T08:41:22.1713632Z  2025-12-04T08:41:22.1713987Z @cache 2025-12-04T08:41:22.1714638Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T08:41:22.1715383Z  """ 2025-12-04T08:41:22.1715807Z  Dynamically get PR information 2025-12-04T08:41:22.1716306Z  """ 2025-12-04T08:41:22.1716869Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T08:41:22.1717518Z  headers = { 2025-12-04T08:41:22.1718113Z  "Accept": "application/vnd.github.v3+json", 2025-12-04T08:41:22.1718729Z  "Authorization": f"token {github_token}", 2025-12-04T08:41:22.1719270Z  } 2025-12-04T08:41:22.1719733Z  json_response: dict[str, Any] = download_json( 2025-12-04T08:41:22.1720378Z  url=f"{github_api}/issues/{pr_number}", 2025-12-04T08:41:22.1720967Z  headers=headers, 2025-12-04T08:41:22.1721442Z  ) 2025-12-04T08:41:22.1721813Z  2025-12-04T08:41:22.1722208Z  if not json_response: 2025-12-04T08:41:22.1722835Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T08:41:22.1723619Z  return {} 2025-12-04T08:41:22.1724038Z  2025-12-04T08:41:22.1724416Z  return json_response 2025-12-04T08:41:22.1724867Z  2025-12-04T08:41:22.1725210Z  2025-12-04T08:41:22.1725822Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T08:41:22.1726527Z  """ 2025-12-04T08:41:22.1727073Z  Dynamically get the latest list of labels from the pull request 2025-12-04T08:41:22.1727722Z  """ 2025-12-04T08:41:22.1728329Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T08:41:22.1728943Z  return { 2025-12-04T08:41:22.1729571Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T08:41:22.1730254Z  } 2025-12-04T08:41:22.1730629Z  2025-12-04T08:41:22.1730990Z  2025-12-04T08:41:22.1731357Z def main() -> None: 2025-12-04T08:41:22.1731813Z  args = parse_args() 2025-12-04T08:41:22.1732294Z  2025-12-04T08:41:22.1732749Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T08:41:22.1733320Z  2025-12-04T08:41:22.1733714Z  # Check if the PR is opt-out 2025-12-04T08:41:22.1734215Z  if args.pr_number: 2025-12-04T08:41:22.1734908Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T08:41:22.1735646Z  if OPT_OUT_LABEL in labels: 2025-12-04T08:41:22.1736162Z  log.info( 2025-12-04T08:41:22.1736888Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T08:41:22.1737646Z  ) 2025-12-04T08:41:22.1738329Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:41:22.1739040Z  sys.exit() 2025-12-04T08:41:22.1739640Z  2025-12-04T08:41:22.1740005Z  try: 2025-12-04T08:41:22.1740493Z  rollout_state = get_rollout_state_from_issue( 2025-12-04T08:41:22.1741215Z  args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T08:41:22.1741849Z  ) 2025-12-04T08:41:22.1742245Z  2025-12-04T08:41:22.1742665Z  username = get_potential_pr_author( 2025-12-04T08:41:22.1743216Z  args.github_token, 2025-12-04T08:41:22.1743731Z  args.github_repo, 2025-12-04T08:41:22.1744254Z  args.github_actor, 2025-12-04T08:41:22.1744784Z  args.github_ref_type, 2025-12-04T08:41:22.1745306Z  args.github_branch, 2025-12-04T08:41:22.1745798Z  ) 2025-12-04T08:41:22.1746214Z  2025-12-04T08:41:22.1746704Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T08:41:22.1747339Z  2025-12-04T08:41:22.1747803Z  runner_label_prefix = get_runner_prefix( 2025-12-04T08:41:22.1748457Z  rollout_state, 2025-12-04T08:41:22.1748984Z  (args.github_issue_owner, username), 2025-12-04T08:41:22.1749558Z  args.github_branch, 2025-12-04T08:41:22.1750110Z  args.eligible_experiments, 2025-12-04T08:41:22.1750660Z  args.opt_out_experiments, 2025-12-04T08:41:22.1751171Z  is_canary, 2025-12-04T08:41:22.1751620Z  ) 2025-12-04T08:41:22.1752008Z  2025-12-04T08:41:22.1752393Z  except Exception as e: 2025-12-04T08:41:22.1752896Z  log.error( 2025-12-04T08:41:22.1753640Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T08:41:22.1754533Z  ) 2025-12-04T08:41:22.1754947Z  2025-12-04T08:41:22.1755485Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:41:22.1756129Z  2025-12-04T08:41:22.1756477Z  2025-12-04T08:41:22.1756854Z if __name__ == "__main__": 2025-12-04T08:41:22.1757345Z  main() 2025-12-04T08:41:22.1757742Z  2025-12-04T08:41:22.1758235Z EOF 2025-12-04T08:41:22.1758612Z  2025-12-04T08:41:22.1759026Z cat runner_determinator.py 2025-12-04T08:41:22.2361379Z shell: /usr/bin/bash -e {0} 2025-12-04T08:41:22.2362184Z env: 2025-12-04T08:41:22.2362849Z GITHUB_TOKEN: *** 2025-12-04T08:41:22.2363301Z ISSUE_NUMBER: 5132 2025-12-04T08:41:22.2363777Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:41:22.2364321Z ISSUE_OWNER: 2025-12-04T08:41:22.2364734Z CHECK_EXPERIMENTS: 2025-12-04T08:41:22.2365200Z OPT_OUT_EXPERIMENTS: 2025-12-04T08:41:22.2365651Z PR_NUMBER: 2025-12-04T08:41:22.2366066Z ##[endgroup] 2025-12-04T08:41:22.2566792Z # flake8: noqa: G004 2025-12-04T08:41:22.2567120Z 2025-12-04T08:41:22.2567547Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T08:41:22.2568721Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T08:41:22.2569493Z # python .github/scripts/update_runner_determinator.py 2025-12-04T08:41:22.2569913Z 2025-12-04T08:41:22.2570081Z """ 2025-12-04T08:41:22.2570670Z This runner determinator is used to determine which set of runners to run a 2025-12-04T08:41:22.2571507Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T08:41:22.2572380Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T08:41:22.2573147Z of which runners should be used to run which job. 2025-12-04T08:41:22.2573535Z 2025-12-04T08:41:22.2573905Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T08:41:22.2574961Z separated by a line containing "---". If the line is not present, the 2025-12-04T08:41:22.2575838Z settings are considered to be empty with only the second part, the user 2025-12-04T08:41:22.2576495Z list, defined. 2025-12-04T08:41:22.2576716Z 2025-12-04T08:41:22.2577071Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T08:41:22.2578220Z used to define any settings that are needed to determine which runners to use. 2025-12-04T08:41:22.2579027Z It's fields are defined by the RolloutSettings class below. 2025-12-04T08:41:22.2579463Z 2025-12-04T08:41:22.2579831Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T08:41:22.2580660Z The user list is also a comma separated list of additional features or 2025-12-04T08:41:22.2581357Z experiments which the user could be opted in to. 2025-12-04T08:41:22.2581739Z 2025-12-04T08:41:22.2581934Z The user list has the following rules: 2025-12-04T08:41:22.2582281Z 2025-12-04T08:41:22.2582602Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T08:41:22.2583420Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T08:41:22.2584150Z - A "#" prefix opts the user out of all experiments 2025-12-04T08:41:22.2584518Z 2025-12-04T08:41:22.2584690Z Example config: 2025-12-04T08:41:22.2585144Z # A list of experiments that can be opted into. 2025-12-04T08:41:22.2585776Z # This defines the behavior they'll induce when opted into. 2025-12-04T08:41:22.2586395Z # Expected syntax is: 2025-12-04T08:41:22.2587005Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T08:41:22.2588182Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T08:41:22.2589045Z 2025-12-04T08:41:22.2589235Z experiments: 2025-12-04T08:41:22.2589647Z lf: 2025-12-04T08:41:22.2590039Z rollout_percent: 25 2025-12-04T08:41:22.2590660Z all_branches: false 2025-12-04T08:41:22.2591105Z default: true 2025-12-04T08:41:22.2591507Z --- 2025-12-04T08:41:22.2591703Z 2025-12-04T08:41:22.2591864Z # Opt-ins: 2025-12-04T08:41:22.2592452Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T08:41:22.2593301Z # and specifying experiments to enable in a comma-separated list. 2025-12-04T08:41:22.2594088Z # To always opt out of an experiment, prefix it with a "-". 2025-12-04T08:41:22.2594720Z # Experiments should be from the above list. 2025-12-04T08:41:22.2595084Z 2025-12-04T08:41:22.2595275Z @User1,-lf,split_build 2025-12-04T08:41:22.2595711Z @User2,lf 2025-12-04T08:41:22.2596113Z @User3,split_build 2025-12-04T08:41:22.2596519Z """ 2025-12-04T08:41:22.2596718Z 2025-12-04T08:41:22.2596885Z import json 2025-12-04T08:41:22.2597248Z import logging 2025-12-04T08:41:22.2597633Z import os 2025-12-04T08:41:22.2598207Z import random 2025-12-04T08:41:22.2598604Z import re 2025-12-04T08:41:22.2598990Z import sys 2025-12-04T08:41:22.2599389Z from argparse import ArgumentParser 2025-12-04T08:41:22.2599912Z from collections.abc import Iterable 2025-12-04T08:41:22.2600412Z from functools import cache 2025-12-04T08:41:22.2600871Z from logging import LogRecord 2025-12-04T08:41:22.2601352Z from typing import Any, NamedTuple 2025-12-04T08:41:22.2601899Z from urllib.request import Request, urlopen 2025-12-04T08:41:22.2602253Z 2025-12-04T08:41:22.2602430Z import yaml 2025-12-04T08:41:22.2602850Z from github import Auth, Github 2025-12-04T08:41:22.2603359Z from github.Issue import Issue 2025-12-04T08:41:22.2603671Z 2025-12-04T08:41:22.2603679Z 2025-12-04T08:41:22.2603909Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T08:41:22.2604567Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T08:41:22.2605400Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T08:41:22.2605940Z 2025-12-04T08:41:22.2606166Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T08:41:22.2606858Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T08:41:22.2607362Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T08:41:22.2608238Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T08:41:22.2608782Z 2025-12-04T08:41:22.2608992Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T08:41:22.2609313Z 2025-12-04T08:41:22.2609506Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T08:41:22.2609962Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T08:41:22.2610236Z 2025-12-04T08:41:22.2610242Z 2025-12-04T08:41:22.2610428Z class Experiment(NamedTuple): 2025-12-04T08:41:22.2610911Z rollout_perc: float = ( 2025-12-04T08:41:22.2611520Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T08:41:22.2612215Z ) 2025-12-04T08:41:22.2612589Z all_branches: bool = ( 2025-12-04T08:41:22.2613198Z False # If True, the experiment is also enabled on the exception branches 2025-12-04T08:41:22.2613864Z ) 2025-12-04T08:41:22.2614248Z default: bool = ( 2025-12-04T08:41:22.2614823Z True # If True, the experiment is enabled by default for all queries 2025-12-04T08:41:22.2615470Z ) 2025-12-04T08:41:22.2615669Z 2025-12-04T08:41:22.2615846Z # Add more fields as needed 2025-12-04T08:41:22.2616145Z 2025-12-04T08:41:22.2616151Z 2025-12-04T08:41:22.2616344Z class Settings(NamedTuple): 2025-12-04T08:41:22.2616787Z """ 2025-12-04T08:41:22.2617251Z Settings for the experiments that can be opted into. 2025-12-04T08:41:22.2617827Z """ 2025-12-04T08:41:22.2618236Z 2025-12-04T08:41:22.2618448Z experiments: dict[str, Experiment] = {} 2025-12-04T08:41:22.2618802Z 2025-12-04T08:41:22.2618807Z 2025-12-04T08:41:22.2619021Z class ColorFormatter(logging.Formatter): 2025-12-04T08:41:22.2619621Z """Color codes the log messages based on the log level""" 2025-12-04T08:41:22.2620048Z 2025-12-04T08:41:22.2620229Z COLORS = { 2025-12-04T08:41:22.2620645Z "WARNING": "\033[33m", # Yellow 2025-12-04T08:41:22.2621289Z "ERROR": "\033[31m", # Red 2025-12-04T08:41:22.2621771Z "CRITICAL": "\033[31m", # Red 2025-12-04T08:41:22.2622251Z "INFO": "\033[0m", # Reset 2025-12-04T08:41:22.2622739Z "DEBUG": "\033[0m", # Reset 2025-12-04T08:41:22.2623186Z } 2025-12-04T08:41:22.2623383Z 2025-12-04T08:41:22.2623598Z def format(self, record: LogRecord) -> str: 2025-12-04T08:41:22.2624316Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T08:41:22.2625062Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T08:41:22.2625623Z return super().format(record) 2025-12-04T08:41:22.2625942Z 2025-12-04T08:41:22.2625948Z 2025-12-04T08:41:22.2626142Z handler = logging.StreamHandler() 2025-12-04T08:41:22.2626817Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T08:41:22.2627339Z 2025-12-04T08:41:22.2627573Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T08:41:22.2628325Z log.addHandler(handler) 2025-12-04T08:41:22.2628767Z log.setLevel(logging.INFO) 2025-12-04T08:41:22.2629051Z 2025-12-04T08:41:22.2629058Z 2025-12-04T08:41:22.2629307Z def set_github_output(key: str, value: str) -> None: 2025-12-04T08:41:22.2629852Z """ 2025-12-04T08:41:22.2630329Z Defines outputs of the github action that invokes this script 2025-12-04T08:41:22.2630938Z """ 2025-12-04T08:41:22.2631304Z if not GITHUB_OUTPUT: 2025-12-04T08:41:22.2632317Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T08:41:22.2633361Z log.warning( 2025-12-04T08:41:22.2634167Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T08:41:22.2635045Z ) 2025-12-04T08:41:22.2644992Z print(f"::set-output name={key}::{value}") 2025-12-04T08:41:22.2645586Z return 2025-12-04T08:41:22.2645831Z 2025-12-04T08:41:22.2646195Z with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T08:41:22.2646786Z log.info(f"Setting output: {key}='{value}'") 2025-12-04T08:41:22.2647363Z f.write(f"{key}={value}\n") 2025-12-04T08:41:22.2647691Z 2025-12-04T08:41:22.2647697Z 2025-12-04T08:41:22.2648249Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T08:41:22.2648864Z return frozenset( 2025-12-04T08:41:22.2649460Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T08:41:22.2650138Z ) 2025-12-04T08:41:22.2650332Z 2025-12-04T08:41:22.2650340Z 2025-12-04T08:41:22.2650524Z def parse_args() -> Any: 2025-12-04T08:41:22.2651083Z parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T08:41:22.2651929Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T08:41:22.2652695Z parser.add_argument( 2025-12-04T08:41:22.2653182Z "--github-issue-repo", 2025-12-04T08:41:22.2653647Z type=str, 2025-12-04T08:41:22.2654042Z required=False, 2025-12-04T08:41:22.2654489Z default="pytorch/test-infra", 2025-12-04T08:41:22.2655017Z help="GitHub repo to get the issue", 2025-12-04T08:41:22.2655507Z ) 2025-12-04T08:41:22.2655868Z parser.add_argument( 2025-12-04T08:41:22.2656290Z "--github-repo", 2025-12-04T08:41:22.2656708Z type=str, 2025-12-04T08:41:22.2657084Z required=True, 2025-12-04T08:41:22.2657542Z help="GitHub repo where CI is running", 2025-12-04T08:41:22.2658282Z ) 2025-12-04T08:41:22.2658649Z parser.add_argument( 2025-12-04T08:41:22.2659234Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T08:41:22.2659849Z ) 2025-12-04T08:41:22.2660209Z parser.add_argument( 2025-12-04T08:41:22.2660823Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T08:41:22.2661464Z ) 2025-12-04T08:41:22.2661817Z parser.add_argument( 2025-12-04T08:41:22.2662617Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T08:41:22.2663289Z ) 2025-12-04T08:41:22.2663645Z parser.add_argument( 2025-12-04T08:41:22.2664270Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T08:41:22.2664940Z ) 2025-12-04T08:41:22.2665309Z parser.add_argument( 2025-12-04T08:41:22.2665747Z "--github-ref-type", 2025-12-04T08:41:22.2666212Z type=str, 2025-12-04T08:41:22.2666604Z required=True, 2025-12-04T08:41:22.2667083Z help="Current GitHub ref type, branch or tag", 2025-12-04T08:41:22.2667631Z ) 2025-12-04T08:41:22.2668187Z parser.add_argument( 2025-12-04T08:41:22.2668620Z "--eligible-experiments", 2025-12-04T08:41:22.2669107Z type=_str_comma_separated_to_set, 2025-12-04T08:41:22.2669602Z required=False, 2025-12-04T08:41:22.2670021Z default="", 2025-12-04T08:41:22.2670847Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T08:41:22.2671721Z ) 2025-12-04T08:41:22.2672078Z parser.add_argument( 2025-12-04T08:41:22.2720779Z "--opt-out-experiments", 2025-12-04T08:41:22.2721404Z type=_str_comma_separated_to_set, 2025-12-04T08:41:22.2721935Z required=False, 2025-12-04T08:41:22.2722363Z default="", 2025-12-04T08:41:22.2722742Z help=( 2025-12-04T08:41:22.2723384Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T08:41:22.2724451Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T08:41:22.2725240Z ), 2025-12-04T08:41:22.2725580Z ) 2025-12-04T08:41:22.2725937Z parser.add_argument( 2025-12-04T08:41:22.2726360Z "--pr-number", 2025-12-04T08:41:22.2726760Z type=str, 2025-12-04T08:41:22.2727151Z required=False, 2025-12-04T08:41:22.2727567Z default="", 2025-12-04T08:41:22.2728315Z help="the optional PR number where this is run", 2025-12-04T08:41:22.2728867Z ) 2025-12-04T08:41:22.2729064Z 2025-12-04T08:41:22.2729255Z return parser.parse_args() 2025-12-04T08:41:22.2729553Z 2025-12-04T08:41:22.2729560Z 2025-12-04T08:41:22.2729953Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T08:41:22.2730664Z auth = Auth.Token(github_token) 2025-12-04T08:41:22.2731145Z return Github(auth=auth) 2025-12-04T08:41:22.2731420Z 2025-12-04T08:41:22.2731427Z 2025-12-04T08:41:22.2731860Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T08:41:22.2732641Z repo = gh.get_repo(repo) 2025-12-04T08:41:22.2733145Z return repo.get_issue(number=issue_num) 2025-12-04T08:41:22.2733494Z 2025-12-04T08:41:22.2733501Z 2025-12-04T08:41:22.2733686Z def get_potential_pr_author( 2025-12-04T08:41:22.2734306Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T08:41:22.2734942Z ) -> str: 2025-12-04T08:41:22.2735441Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T08:41:22.2736231Z # Fetch the actual username from the original PR. The PR number is 2025-12-04T08:41:22.2736952Z # embedded in the tag name: ciflow// 2025-12-04T08:41:22.2737332Z 2025-12-04T08:41:22.2737518Z gh = get_gh_client(github_token) 2025-12-04T08:41:22.2737948Z 2025-12-04T08:41:22.2738208Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T08:41:22.2738801Z split_tag = ref_name.split("/") 2025-12-04T08:41:22.2739270Z if ( 2025-12-04T08:41:22.2739644Z len(split_tag) == 3 2025-12-04T08:41:22.2740095Z and split_tag[0] == "ciflow" 2025-12-04T08:41:22.2740597Z and split_tag[2].isnumeric() 2025-12-04T08:41:22.2741067Z ): 2025-12-04T08:41:22.2741440Z pr_number = split_tag[2] 2025-12-04T08:41:22.2742057Z try: 2025-12-04T08:41:22.2742470Z repository = gh.get_repo(repo) 2025-12-04T08:41:22.2743048Z pull = repository.get_pull(number=int(pr_number)) 2025-12-04T08:41:22.2743611Z except Exception as e: 2025-12-04T08:41:22.2744109Z raise Exception( # noqa: TRY002 2025-12-04T08:41:22.2744744Z f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T08:41:22.2745357Z ) from e 2025-12-04T08:41:22.2745871Z return pull.user.login # type: ignore[no-any-return] 2025-12-04T08:41:22.2746538Z # In all other cases, return the original input username 2025-12-04T08:41:22.2747131Z return username 2025-12-04T08:41:22.2747359Z 2025-12-04T08:41:22.2747365Z 2025-12-04T08:41:22.2747586Z def is_exception_branch(branch: str) -> bool: 2025-12-04T08:41:22.2748236Z """ 2025-12-04T08:41:22.2748842Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T08:41:22.2749587Z """ 2025-12-04T08:41:22.2750101Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T08:41:22.2750759Z 2025-12-04T08:41:22.2750769Z 2025-12-04T08:41:22.2750965Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T08:41:22.2751444Z try: 2025-12-04T08:41:22.2751813Z data = yaml.safe_load(yaml_text) 2025-12-04T08:41:22.2752291Z return data 2025-12-04T08:41:22.2752693Z except yaml.YAMLError: 2025-12-04T08:41:22.2753154Z log.exception("Error loading YAML") 2025-12-04T08:41:22.2753637Z raise 2025-12-04T08:41:22.2753849Z 2025-12-04T08:41:22.2753855Z 2025-12-04T08:41:22.2754245Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T08:41:22.2754925Z """ 2025-12-04T08:41:22.2755534Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T08:41:22.2756101Z 2025-12-04T08:41:22.2756580Z If the issue body contains "---" then the text above that is the settings 2025-12-04T08:41:22.2757301Z and the text below is the list of opted in users. 2025-12-04T08:41:22.2757686Z 2025-12-04T08:41:22.2758156Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T08:41:22.2758807Z """ 2025-12-04T08:41:22.2759247Z rollout_state_parts = rollout_state.split("---") 2025-12-04T08:41:22.2759810Z if len(rollout_state_parts) >= 2: 2025-12-04T08:41:22.2760387Z return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T08:41:22.2760941Z else: 2025-12-04T08:41:22.2761303Z return "", rollout_state 2025-12-04T08:41:22.2761592Z 2025-12-04T08:41:22.2761599Z 2025-12-04T08:41:22.2761795Z class UserOptins(dict[str, list[str]]): 2025-12-04T08:41:22.2762268Z """ 2025-12-04T08:41:22.2762770Z Dictionary of users with a list of features they have opted into 2025-12-04T08:41:22.2763401Z """ 2025-12-04T08:41:22.2763603Z 2025-12-04T08:41:22.2763609Z 2025-12-04T08:41:22.2763934Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T08:41:22.2764551Z """ 2025-12-04T08:41:22.2765212Z 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:41:22.2765843Z 2025-12-04T08:41:22.2766427Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T08:41:22.2767353Z - Example line: "@User1,lf,split_build" 2025-12-04T08:41:22.2768127Z - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T08:41:22.2768573Z 2025-12-04T08:41:22.2768579Z 2025-12-04T08:41:22.2768743Z """ 2025-12-04T08:41:22.2769107Z optins = UserOptins() 2025-12-04T08:41:22.2769568Z for user in user_optin_text.split("\n"): 2025-12-04T08:41:22.2770087Z user = user.strip("\r\n\t -") 2025-12-04T08:41:22.2770605Z if not user or not user.startswith("@"): 2025-12-04T08:41:22.2771267Z # Not a valid user. Skip 2025-12-04T08:41:22.2771728Z continue 2025-12-04T08:41:22.2771955Z 2025-12-04T08:41:22.2772107Z if user: 2025-12-04T08:41:22.2772525Z usr_name = user.split(",")[0].strip("@") 2025-12-04T08:41:22.2773176Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T08:41:22.2773640Z 2025-12-04T08:41:22.2773799Z return optins 2025-12-04T08:41:22.2774021Z 2025-12-04T08:41:22.2774034Z 2025-12-04T08:41:22.2774306Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T08:41:22.2774873Z """ 2025-12-04T08:41:22.2775255Z Check if the experiment name is valid. 2025-12-04T08:41:22.2775749Z A valid name: 2025-12-04T08:41:22.2776374Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T08:41:22.2777261Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T08:41:22.2778049Z - Cannot contain spaces 2025-12-04T08:41:22.2778501Z """ 2025-12-04T08:41:22.2778690Z 2025-12-04T08:41:22.2778941Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T08:41:22.2779609Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T08:41:22.2780037Z 2025-12-04T08:41:22.2780197Z if valid: 2025-12-04T08:41:22.2780612Z return True 2025-12-04T08:41:22.2780944Z 2025-12-04T08:41:22.2781119Z log.error( 2025-12-04T08:41:22.2782460Z 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:41:22.2783925Z ) 2025-12-04T08:41:22.2784263Z return False 2025-12-04T08:41:22.2784492Z 2025-12-04T08:41:22.2784498Z 2025-12-04T08:41:22.2784783Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T08:41:22.2785385Z """ 2025-12-04T08:41:22.2786102Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T08:41:22.2786819Z """ 2025-12-04T08:41:22.2787149Z try: 2025-12-04T08:41:22.2787510Z if settings_text: 2025-12-04T08:41:22.2788608Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T08:41:22.2789398Z # for easy reading 2025-12-04T08:41:22.2790137Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T08:41:22.2790965Z # the backtick character in shell commands. 2025-12-04T08:41:22.2791539Z backtick = chr(96) # backtick character 2025-12-04T08:41:22.2792161Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T08:41:22.2792806Z settings = load_yaml(settings_text) 2025-12-04T08:41:22.2793151Z 2025-12-04T08:41:22.2793543Z # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T08:41:22.2794255Z experiments = {} 2025-12-04T08:41:22.2794528Z 2025-12-04T08:41:22.2794901Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T08:41:22.2795620Z if not is_valid_experiment_name(exp_name): 2025-12-04T08:41:22.2796682Z # 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:41:22.2797675Z continue 2025-12-04T08:41:22.2798054Z 2025-12-04T08:41:22.2798229Z valid_settings = {} 2025-12-04T08:41:22.2798723Z for setting in exp_settings: 2025-12-04T08:41:22.2799259Z if setting not in Experiment._fields: 2025-12-04T08:41:22.2799788Z log.warning( 2025-12-04T08:41:22.2800444Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T08:41:22.2801271Z ) 2025-12-04T08:41:22.2801680Z else: 2025-12-04T08:41:22.2802172Z valid_settings[setting] = exp_settings[setting] 2025-12-04T08:41:22.2802565Z 2025-12-04T08:41:22.2802828Z experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T08:41:22.2803427Z return Settings(experiments) 2025-12-04T08:41:22.2803753Z 2025-12-04T08:41:22.2803926Z except Exception: 2025-12-04T08:41:22.2804380Z log.exception("Failed to parse settings") 2025-12-04T08:41:22.2804744Z 2025-12-04T08:41:22.2804909Z return Settings() 2025-12-04T08:41:22.2805145Z 2025-12-04T08:41:22.2805152Z 2025-12-04T08:41:22.2805390Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T08:41:22.2805925Z """ 2025-12-04T08:41:22.2806343Z Parse settings, if any, from the rollout state. 2025-12-04T08:41:22.2806728Z 2025-12-04T08:41:22.2807076Z If the issue body contains "---" then the text above that is the settings 2025-12-04T08:41:22.2807932Z and the text below is the list of opted in users. 2025-12-04T08:41:22.2808330Z 2025-12-04T08:41:22.2808728Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T08:41:22.2809421Z """ 2025-12-04T08:41:22.2809936Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:41:22.2810635Z return parse_settings_from_text(settings_text) 2025-12-04T08:41:22.2811001Z 2025-12-04T08:41:22.2811006Z 2025-12-04T08:41:22.2811244Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T08:41:22.2811757Z """ 2025-12-04T08:41:22.2812159Z Parse users from the rollout state. 2025-12-04T08:41:22.2812484Z 2025-12-04T08:41:22.2812644Z """ 2025-12-04T08:41:22.2813149Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:41:22.2813850Z return parse_user_opt_in_from_text(users_text) 2025-12-04T08:41:22.2814226Z 2025-12-04T08:41:22.2814232Z 2025-12-04T08:41:22.2814732Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:41:22.2815429Z """ 2025-12-04T08:41:22.2815817Z Check if a user is opted into an experiment 2025-12-04T08:41:22.2816333Z """ 2025-12-04T08:41:22.2816773Z return experiment_name in user_optins.get(user, []) 2025-12-04T08:41:22.2817166Z 2025-12-04T08:41:22.2817173Z 2025-12-04T08:41:22.2817564Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:41:22.2818384Z """ 2025-12-04T08:41:22.2818832Z Check if a user explicitly opted out of an experiment 2025-12-04T08:41:22.2819376Z """ 2025-12-04T08:41:22.2819846Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T08:41:22.2820486Z experiment_optout = "-" + experiment_name 2025-12-04T08:41:22.2821076Z if experiment_optout not in user_optins.get(user, []): 2025-12-04T08:41:22.2821636Z return False 2025-12-04T08:41:22.2821872Z 2025-12-04T08:41:22.2822144Z if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T08:41:22.2822745Z log.warning( 2025-12-04T08:41:22.2823504Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T08:41:22.2824317Z ) 2025-12-04T08:41:22.2824513Z 2025-12-04T08:41:22.2824678Z return True 2025-12-04T08:41:22.2824894Z 2025-12-04T08:41:22.2824900Z 2025-12-04T08:41:22.2825073Z def get_runner_prefix( 2025-12-04T08:41:22.2825486Z rollout_state: str, 2025-12-04T08:41:22.2825918Z workflow_requestors: Iterable[str], 2025-12-04T08:41:22.2826407Z branch: str, 2025-12-04T08:41:22.2826866Z eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T08:41:22.2827474Z opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T08:41:22.2828119Z is_canary: bool = False, 2025-12-04T08:41:22.2828538Z ) -> str: 2025-12-04T08:41:22.2829055Z settings = parse_settings(rollout_state) 2025-12-04T08:41:22.2829603Z user_optins = parse_users(rollout_state) 2025-12-04T08:41:22.2829966Z 2025-12-04T08:41:22.2830131Z fleet_prefix = "" 2025-12-04T08:41:22.2830522Z prefixes = [] 2025-12-04T08:41:22.2831114Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T08:41:22.2831985Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T08:41:22.2832658Z log.info( 2025-12-04T08:41:22.2833289Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T08:41:22.2833995Z ) 2025-12-04T08:41:22.2834358Z continue 2025-12-04T08:41:22.2834584Z 2025-12-04T08:41:22.2834757Z if opt_out_experiments: 2025-12-04T08:41:22.2835255Z if experiment_name in opt_out_experiments: 2025-12-04T08:41:22.2835851Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T08:41:22.2836404Z log.info( 2025-12-04T08:41:22.2837272Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T08:41:22.2838308Z ) 2025-12-04T08:41:22.2838705Z continue 2025-12-04T08:41:22.2838972Z 2025-12-04T08:41:22.2839168Z if eligible_experiments: 2025-12-04T08:41:22.2839711Z if experiment_name not in eligible_experiments: 2025-12-04T08:41:22.2840330Z exp_list = ", ".join(eligible_experiments) 2025-12-04T08:41:22.2840892Z log.info( 2025-12-04T08:41:22.2841656Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T08:41:22.2842455Z ) 2025-12-04T08:41:22.2842834Z continue 2025-12-04T08:41:22.2843279Z elif not experiment_settings.default: 2025-12-04T08:41:22.2843775Z log.info( 2025-12-04T08:41:22.2844541Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T08:41:22.2845240Z ) 2025-12-04T08:41:22.2845594Z continue 2025-12-04T08:41:22.2845824Z 2025-12-04T08:41:22.2846090Z # Is any workflow_requestor opted out to this experiment? 2025-12-04T08:41:22.2846665Z opted_out_users = [ 2025-12-04T08:41:22.2847083Z requestor 2025-12-04T08:41:22.2847506Z for requestor in workflow_requestors 2025-12-04T08:41:22.2848231Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T08:41:22.2848824Z ] 2025-12-04T08:41:22.2849018Z 2025-12-04T08:41:22.2849190Z if opted_out_users: 2025-12-04T08:41:22.2849608Z log.info( 2025-12-04T08:41:22.2850178Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T08:41:22.2850819Z ) 2025-12-04T08:41:22.2851171Z continue 2025-12-04T08:41:22.2851405Z 2025-12-04T08:41:22.2851660Z # Is any workflow_requestor opted in to this experiment? 2025-12-04T08:41:22.2852226Z opted_in_users = [ 2025-12-04T08:41:22.2852632Z requestor 2025-12-04T08:41:22.2853051Z for requestor in workflow_requestors 2025-12-04T08:41:22.2853664Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T08:41:22.2854244Z ] 2025-12-04T08:41:22.2854430Z 2025-12-04T08:41:22.2854594Z enabled = False 2025-12-04T08:41:22.2854999Z if opted_in_users: 2025-12-04T08:41:22.2855407Z log.info( 2025-12-04T08:41:22.2855963Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T08:41:22.2856612Z ) 2025-12-04T08:41:22.2856970Z enabled = True 2025-12-04T08:41:22.2857232Z 2025-12-04T08:41:22.2857441Z elif experiment_settings.rollout_perc: 2025-12-04T08:41:22.2858305Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T08:41:22.2859315Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T08:41:22.2859926Z log.info( 2025-12-04T08:41:22.2860720Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T08:41:22.2861606Z ) 2025-12-04T08:41:22.2861998Z enabled = True 2025-12-04T08:41:22.2862278Z 2025-12-04T08:41:22.2862434Z if enabled: 2025-12-04T08:41:22.2862827Z label = experiment_name 2025-12-04T08:41:22.2863335Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T08:41:22.2864095Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T08:41:22.2864905Z # - If it's enabled, then we always list it's prefix first 2025-12-04T08:41:22.2865614Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T08:41:22.2866222Z if is_canary: 2025-12-04T08:41:22.2866673Z label += CANARY_FLEET_SUFFIX 2025-12-04T08:41:22.2867181Z fleet_prefix = label 2025-12-04T08:41:22.2867640Z else: 2025-12-04T08:41:22.2868149Z prefixes.append(label) 2025-12-04T08:41:22.2868474Z 2025-12-04T08:41:22.2868647Z if len(prefixes) > 1: 2025-12-04T08:41:22.2869061Z log.error( 2025-12-04T08:41:22.2870012Z 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:41:22.2871066Z ) 2025-12-04T08:41:22.2871425Z prefixes = prefixes[:1] 2025-12-04T08:41:22.2871712Z 2025-12-04T08:41:22.2871887Z # Fleet always comes first 2025-12-04T08:41:22.2872327Z if fleet_prefix: 2025-12-04T08:41:22.2872752Z prefixes.insert(0, fleet_prefix) 2025-12-04T08:41:22.2873104Z 2025-12-04T08:41:22.2873474Z return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T08:41:22.2873874Z 2025-12-04T08:41:22.2873881Z 2025-12-04T08:41:22.2874293Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T08:41:22.2875011Z """ 2025-12-04T08:41:22.2875551Z Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T08:41:22.2876074Z 2025-12-04T08:41:22.2876452Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T08:41:22.2877109Z """ 2025-12-04T08:41:22.2877468Z gh = get_gh_client(github_token) 2025-12-04T08:41:22.2878073Z issue = get_issue(gh, repo, issue_num) 2025-12-04T08:41:22.2878658Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T08:41:22.2879066Z 2025-12-04T08:41:22.2879073Z 2025-12-04T08:41:22.2879440Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T08:41:22.2880140Z for _ in range(num_retries): 2025-12-04T08:41:22.2880593Z try: 2025-12-04T08:41:22.2880994Z req = Request(url=url, headers=headers) 2025-12-04T08:41:22.2881608Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T08:41:22.2882203Z return json.loads(content) 2025-12-04T08:41:22.2882698Z except Exception as e: 2025-12-04T08:41:22.2883198Z log.warning(f"Could not download {url}: {e}") 2025-12-04T08:41:22.2883568Z 2025-12-04T08:41:22.2883934Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T08:41:22.2884613Z return {} 2025-12-04T08:41:22.2884824Z 2025-12-04T08:41:22.2884831Z 2025-12-04T08:41:22.2884989Z @cache 2025-12-04T08:41:22.2885566Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T08:41:22.2886256Z """ 2025-12-04T08:41:22.2886625Z Dynamically get PR information 2025-12-04T08:41:22.2887214Z """ 2025-12-04T08:41:22.2887675Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T08:41:22.2888371Z headers = { 2025-12-04T08:41:22.2888795Z "Accept": "application/vnd.github.v3+json", 2025-12-04T08:41:22.2889364Z "Authorization": f"token {github_token}", 2025-12-04T08:41:22.2889859Z } 2025-12-04T08:41:22.2890276Z json_response: dict[str, Any] = download_json( 2025-12-04T08:41:22.2890831Z url=f"{github_api}/issues/{pr_number}", 2025-12-04T08:41:22.2891341Z headers=headers, 2025-12-04T08:41:22.2891740Z ) 2025-12-04T08:41:22.2891930Z 2025-12-04T08:41:22.2892103Z if not json_response: 2025-12-04T08:41:22.2892628Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T08:41:22.2893211Z return {} 2025-12-04T08:41:22.2893433Z 2025-12-04T08:41:22.2893598Z return json_response 2025-12-04T08:41:22.2893852Z 2025-12-04T08:41:22.2893859Z 2025-12-04T08:41:22.2894229Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T08:41:22.2894928Z """ 2025-12-04T08:41:22.2895432Z Dynamically get the latest list of labels from the pull request 2025-12-04T08:41:22.2896027Z """ 2025-12-04T08:41:22.2896470Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T08:41:22.2897028Z return { 2025-12-04T08:41:22.2897569Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T08:41:22.2898334Z } 2025-12-04T08:41:22.2898528Z 2025-12-04T08:41:22.2898535Z 2025-12-04T08:41:22.2898695Z def main() -> None: 2025-12-04T08:41:22.2899087Z args = parse_args() 2025-12-04T08:41:22.2899336Z 2025-12-04T08:41:22.2899544Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T08:41:22.2899893Z 2025-12-04T08:41:22.2900077Z # Check if the PR is opt-out 2025-12-04T08:41:22.2900525Z if args.pr_number: 2025-12-04T08:41:22.2901127Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T08:41:22.2901944Z if OPT_OUT_LABEL in labels: 2025-12-04T08:41:22.2902417Z log.info( 2025-12-04T08:41:22.2903052Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T08:41:22.2903771Z ) 2025-12-04T08:41:22.2904277Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:41:22.2904890Z sys.exit() 2025-12-04T08:41:22.2905139Z 2025-12-04T08:41:22.2905313Z try: 2025-12-04T08:41:22.2905752Z rollout_state = get_rollout_state_from_issue( 2025-12-04T08:41:22.2906440Z args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T08:41:22.2907056Z ) 2025-12-04T08:41:22.2907249Z 2025-12-04T08:41:22.2907440Z username = get_potential_pr_author( 2025-12-04T08:41:22.2908047Z args.github_token, 2025-12-04T08:41:22.2908496Z args.github_repo, 2025-12-04T08:41:22.2908937Z args.github_actor, 2025-12-04T08:41:22.2909379Z args.github_ref_type, 2025-12-04T08:41:22.2909863Z args.github_branch, 2025-12-04T08:41:22.2910289Z ) 2025-12-04T08:41:22.2910484Z 2025-12-04T08:41:22.2910745Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T08:41:22.2911157Z 2025-12-04T08:41:22.2911365Z runner_label_prefix = get_runner_prefix( 2025-12-04T08:41:22.2911880Z rollout_state, 2025-12-04T08:41:22.2912363Z (args.github_issue_owner, username), 2025-12-04T08:41:22.2912872Z args.github_branch, 2025-12-04T08:41:22.2913331Z args.eligible_experiments, 2025-12-04T08:41:22.2913824Z args.opt_out_experiments, 2025-12-04T08:41:22.2914305Z is_canary, 2025-12-04T08:41:22.2914716Z ) 2025-12-04T08:41:22.2914929Z 2025-12-04T08:41:22.2915115Z except Exception as e: 2025-12-04T08:41:22.2915532Z log.error( 2025-12-04T08:41:22.2916156Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T08:41:22.2917050Z ) 2025-12-04T08:41:22.2917265Z 2025-12-04T08:41:22.2917571Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:41:22.2918128Z 2025-12-04T08:41:22.2918135Z 2025-12-04T08:41:22.2918305Z if __name__ == "__main__": 2025-12-04T08:41:22.2918716Z main() 2025-12-04T08:41:22.2918924Z 2025-12-04T08:41:22.3003600Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T08:41:22.3004455Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T08:41:22.3036498Z shell: /usr/bin/bash -e {0} 2025-12-04T08:41:22.3036987Z env: 2025-12-04T08:41:22.3037598Z GITHUB_TOKEN: *** 2025-12-04T08:41:22.3038151Z ISSUE_NUMBER: 5132 2025-12-04T08:41:22.3038596Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:41:22.3039081Z ISSUE_OWNER: 2025-12-04T08:41:22.3039484Z CHECK_EXPERIMENTS: 2025-12-04T08:41:22.3039920Z OPT_OUT_EXPERIMENTS: 2025-12-04T08:41:22.3040363Z PR_NUMBER: 2025-12-04T08:41:22.3040727Z ##[endgroup] 2025-12-04T08:41:22.8176966Z Defaulting to user installation because normal site-packages is not writeable 2025-12-04T08:41:23.3463821Z Collecting urllib3==1.26.18 2025-12-04T08:41:23.3887385Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-12-04T08:41:23.4098012Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.0 MB/s eta 0:00:00 2025-12-04T08:41:23.4311241Z Collecting PyGithub==2.3.0 2025-12-04T08:41:23.4346820Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-12-04T08:41:23.4810044Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-12-04T08:41:23.4843996Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.8 kB) 2025-12-04T08:41:23.4889179Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-12-04T08:41:23.4907581Z 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:41:23.4923228Z 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:41:23.5198146Z Collecting Deprecated (from PyGithub==2.3.0) 2025-12-04T08:41:23.5232426Z Downloading deprecated-1.3.1-py2.py3-none-any.whl.metadata (5.9 kB) 2025-12-04T08:41:23.5461196Z 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:41:23.6725545Z Collecting cffi>=2.0.0 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T08:41:23.6764781Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-12-04T08:41:23.8352901Z Collecting wrapt<3,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-12-04T08:41:23.8390172Z 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:41:23.8583660Z Collecting pycparser (from cffi>=2.0.0->pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T08:41:23.8614574Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-12-04T08:41:23.8846824Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-12-04T08:41:23.9082372Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 15.3 MB/s eta 0:00:00 2025-12-04T08:41:23.9132905Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-12-04T08:41:23.9207656Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 59.4 MB/s eta 0:00:00 2025-12-04T08:41:23.9241104Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-12-04T08:41:23.9347730Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 153.1 MB/s eta 0:00:00 2025-12-04T08:41:23.9380019Z Downloading deprecated-1.3.1-py2.py3-none-any.whl (11 kB) 2025-12-04T08:41:23.9432055Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-12-04T08:41:23.9479532Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 66.5 MB/s eta 0:00:00 2025-12-04T08:41:23.9511877Z 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:41:23.9553620Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.5/121.5 kB 41.9 MB/s eta 0:00:00 2025-12-04T08:41:23.9586559Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-12-04T08:41:23.9630201Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 46.1 MB/s eta 0:00:00 2025-12-04T08:41:24.2580829Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-12-04T08:41:24.7942456Z 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:41:24.8726616Z ##[group]Run curr_branch="main" 2025-12-04T08:41:24.8726946Z curr_branch="main" 2025-12-04T08:41:24.8727172Z curr_ref_type="branch" 2025-12-04T08:41:24.8727433Z echo "Current branch is '$curr_branch'" 2025-12-04T08:41:24.8727730Z  2025-12-04T08:41:24.8728521Z python3 runner_determinator.py \ 2025-12-04T08:41:24.8728834Z  --github-token "$GITHUB_TOKEN" \ 2025-12-04T08:41:24.8729117Z  --github-issue "$ISSUE_NUMBER" \ 2025-12-04T08:41:24.8729379Z  --github-branch "$curr_branch" \ 2025-12-04T08:41:24.8729665Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-12-04T08:41:24.8729968Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-12-04T08:41:24.8730250Z  --github-ref-type "$curr_ref_type" \ 2025-12-04T08:41:24.8730545Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-12-04T08:41:24.8730898Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-12-04T08:41:24.8731279Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-12-04T08:41:24.8731587Z  --pr-number "${PR_NUMBER}" 2025-12-04T08:41:24.8764721Z shell: /usr/bin/bash -e {0} 2025-12-04T08:41:24.8764955Z env: 2025-12-04T08:41:24.8765545Z GITHUB_TOKEN: *** 2025-12-04T08:41:24.8765747Z ISSUE_NUMBER: 5132 2025-12-04T08:41:24.8765952Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:41:24.8766198Z ISSUE_OWNER: 2025-12-04T08:41:24.8766389Z CHECK_EXPERIMENTS: 2025-12-04T08:41:24.8766586Z OPT_OUT_EXPERIMENTS: 2025-12-04T08:41:24.8766787Z PR_NUMBER: 2025-12-04T08:41:24.8766952Z ##[endgroup] 2025-12-04T08:41:24.8817320Z Current branch is 'main' 2025-12-04T08:41:26.4591242Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-12-04T08:41:26.4592595Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-12-04T08:41:26.4593834Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-12-04T08:41:26.4594893Z INFO : Setting output: label-type='' 2025-12-04T08:41:26.4909388Z Evaluate and set job outputs 2025-12-04T08:41:26.4917047Z Cleaning up orphan processes