# Technical Documentation: actions/runner

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/actions/runner)

### Overview (`README.md`)

<p align="center">
  <img src="docs/res/github-graph.png">
</p>

# GitHub Actions Runner

[](https://github.com/actions/runner/actions)

The runner is the application that runs a job from a GitHub Actions workflow. It is used by GitHub Actions in the [hosted virtual environments](https://github.com/actions/virtual-environments), or you can [self-host the runner](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners) in your own environment.

## Get Started

For more information about installing and using self-hosted runners, see [Adding self-hosted runners](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/adding-self-hosted-runners) and [Using self-hosted runners in a workflow](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-self-hosted-runners-in-a-workflow)

Runner releases:

 [Pre-reqs](docs/start/envwin.md) | [Download](https://github.com/actions/runner/releases)  

  [Pre-reqs](docs/start/envosx.md) | [Download](https://github.com/actions/runner/releases)  

  [Pre-reqs](docs/start/envlinux.md) | [Download](https://github.com/actions/runner/releases)

### Note

Thank you for your interest in this GitHub repo, however, right now we are not taking contributions. 

We continue to focus our resources on strategic areas that help our customers be successful while making developers' lives easier. While GitHub Actions remains a key part of this vision, we are allocating resources towards other areas of Actions and are not taking contributions to this repository at this time. The GitHub public roadmap is the best place to follow along for any updates on features we’re working on and what stage they’re in.

We are taking the following steps to better direct requests related to GitHub Actions, including:

1. We will be directing questions and support requests to our [Community Discussions area](https://github.com/orgs/community/discussions/categories/actions)

2. High Priority bugs can be reported through Community Discussions or you can report these to our support team https://support.github.com/contact/bug-report.

3. Security Issues should be handled as per our [SECURITY.md](https://github.com/actions/runner?tab=security-ov-file)

We will still provide security updates for this project and fix major breaking changes during this time.

You are welcome to still raise bugs in this repo.

---

### Overview (`scripts/README.md`)

# Sample scripts for self-hosted runners

Here are some examples to work from if you'd like to automate your use of self-hosted runners.
See the docs [here](../docs/automate.md).

---

### Overview (`docs/adrs/README.md`)

# ADRs

ADR, short for "Architecture Decision Record" is a way of capturing important architectural decisions, along with their context and consequences.

This folder includes ADRs for the actions runner. ADRs are proposed in the form of a pull request, and they commonly follow this format:

* **Title**: short present tense imperative phrase, less than 50 characters, like a git commit message.

* **Status**: proposed, accepted, rejected, deprecated, superseded, etc.

* **Context**: what is the issue that we're seeing that is motivating this decision or change.

* **Decision**: what is the change that we're actually proposing or doing.

* **Consequences**: what becomes easier or more difficult to do because of this change.

---

- More information about ADRs can be found [here](https://github.com/joelparkerhenderson/architecture_decision_record).

---

### Overview (`src/Misc/expressionFunc/hashFiles/README.md`)

To compile this package (output will be stored in `Misc/layoutbin`) run `npm install && npm run prepare && npm run all`.

When you commit changes to the JSON or Typescript file, the javascript binary will be automatically re-compiled and added to the latest commit.

---

### Automate (`docs/automate.md`)

# Automate Configuring Self-Hosted Runners


## Export PAT

Before running any of these sample scripts, create a GitHub PAT and export it before running the script

```bash
export RUNNER_CFG_PAT=yourPAT
```

## Create running as a service

**Scenario**: Run on a machine or VM ([not container](#why-cant-i-use-a-container)) which automates:

 - Resolving latest released runner
 - Download and extract latest
 - Acquire a registration token
 - Configure the runner
 - Run as a systemd (linux) or Launchd (osx) service

:point_right: [Sample script here](../scripts/create-latest-svc.sh) :point_left:

Run as a one-liner. NOTE: replace with yourorg/yourrepo (repo level) or just yourorg (org level)
```bash
curl -s https://raw.githubusercontent.com/actions/runner/main/scripts/create-latest-svc.sh | bash -s yourorg/yourrepo
```

You can call the script with additional arguments:
```bash
#   Usage:
#       export RUNNER_CFG_PAT=<yourPAT>
#       ./create-latest-svc -s scope -g [ghe_domain] -n [name] -u [user] -l [labels]
#       -s          required  scope: repo (:owner/:repo) or org (:organization)
#       -g          optional  ghe_hostname: the fully qualified domain name of your GitHub Enterprise Server deployment
#       -n          optional  name of the runner, defaults to hostname
#       -u          optional  user svc will run as, defaults to current
#       -l          optional  list of labels (split by comma) applied on the runner"
```

Use `--` to pass any number of optional named parameters:

```
curl -s https://raw.githubusercontent.com/actions/runner/main/scripts/create-latest-svc.sh | bash -s -- -s myorg/myrepo -n myname -l label1,label2
```
### Why can't I use a container?

The runner is installed as a service using `systemd` and `systemctl`. Docker does not support `systemd` for service configuration on a container.

## Uninstall running as service

**Scenario**: Run on a machine or VM ([not container](#why-cant-i-use-a-container)) which automates:

 - Stops and uninstalls the systemd (linux) or Launchd (osx) service
 - Acquires a removal token
 - Removes the runner

:point_right: [Sample script here](../scripts/remove-svc.sh) :point_left:

Repo level one liner.  NOTE: replace with yourorg/yourrepo (repo level) or just yourorg (org level)
```bash
curl -s https://raw.githubusercontent.com/actions/runner/main/scripts/remove-svc.sh | bash -s yourorg/yourrepo
```

### Delete an offline runner

**Scenario**: Deletes a registered runner that is offline:

 - Ensures the runner is offline
 - Resolves id from name
 - Deletes the runner

:point_right: [Sample script here](../scripts/delete.sh) :point_left:

Repo level one-liner.  NOTE: replace with yourorg/yourrepo (repo level) or just yourorg (org level) and replace runnername
```bash
curl -s https://raw.githubusercontent.com/actions/runner/main/scripts/delete.sh | bash -s yourorg/yourrepo runnername
```

---

### Contribute (`docs/contribute.md`)

# Contributions

We welcome contributions in the form of issues and pull requests. We view the contributions and the process as the same for github and external contributors. Please note the runner typically requires changes across the entire system and we aim for issues in the runner to be entirely self contained and fixable here. Therefore, we will primarily handle bug issues opened in this repo and we kindly request you to create all feature and enhancement requests on the [GitHub Feedback](https://github.com/community/community/discussions/categories/actions-and-packages) page. 

> IMPORTANT: Building your own runner is critical for the dev inner loop process when contributing changes.  However, only runners built and distributed by GitHub (releases) are supported in production.  Be aware that workflows and orchestrations run service side with the runner being a remote process to run steps.  For that reason, the service can pull the runner forward so customizations can be lost.

## Issues

Log issues for both bugs and enhancement requests.  Logging issues are important for the open community.

Issues in this repository should be for the runner application.  Note that the VM and virtual machine images (including the developer toolsets) installed on the actions hosted machine pools are located [in this repository](https://github.com/actions/virtual-environments)

## Enhancements and Feature Requests

We ask that before significant effort is put into code changes, that we have agreement on taking the change before time is invested in code changes.

1. Create a feature request.  Once agreed we will take the enhancement
2. Create an ADR to agree on the details of the change.

An ADR is an Architectural Decision Record.  This allows consensus on the direction forward and also serves as a record of the change and motivation.  [Read more here](adrs/README.md)

## Required Dev Dependencies

   Git for Windows and Linux [Install Here](https://git-scm.com/downloads) (needed for dev sh script)

 cURL [Install here](https://curl.se/download.html) (needed for external sh script)

 Visual Studio 2017 or newer [Install here](https://visualstudio.microsoft.com) (needed for dev sh script)

 Visual Studio 2022 17.3 Preview or later. [Install here](https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-preview)

## Quickstart: Run a job from a real repository

If you just want to get from building the sourcecode to using it to execute an action, you will need:

- The url of your repository
- A runner registration token. You can find it at `https://github.com/{your-repo}/settings/actions/runners/new`


```bash
git clone https://github.com/actions/runner
cd runner/src
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout
cd ../_layout
./config.(sh/cmd) --url https://github.com/{your-repo} --token ABCABCABCABCABCABCABCABCABCAB # accept default name, labels and work folder
./run.(sh/cmd)
```

If you trigger a job now, you can see the runner execute it.

Tip: Make sure your job can run on this runner. The easiest way is to set `runs-on: self-hosted` in the workflow file.


## Development Life Cycle
If you're using VS Code, you can follow [these](contribute/vscode.md) steps instead.

### To Build, Test, Layout

Navigate to the `src` directory and run the following command:

 `dev {command}`  

 `./dev.sh {command}`
  
**Commands:**  

* `layout` (`l`):  Run first time to create a full runner layout in `{root}/_layout`
* `build` (`b`):   Build everything and update runner layout folder
* `test` (`t`):    Build runner binaries and run unit tests

### Sample developer flow:

```bash
git clone https://github.com/actions/runner
cd runner
cd ./src
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout
<make code changes>
./dev.(sh/cmd) build # {root}/_layout will get updated
./dev.(sh/cmd) test # run all unit tests before git commit/push
```

Let's break that down.

### Clone repository:

```bash
git clone https://github.com/actions/runner
cd runner
```
If you want to push your changes to a remote, it is recommended you fork the repository and use that fork as your origin instead of `https://github.com/actions/runner`.


### Build Layout:

This command will build all projects, then copies them and other dependencies into a folder called `_layout`. The binaries in this folder are then used for running, debugging the runner.

```bash
cd ./src # execute the script from this folder
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout
```

If you make code changes after this point, use the argument `build` to build your code in the `src` folder to keep your `_layout` folder up to date.

```bash
cd ./src
./dev.(sh/cmd) build # {root}/_layout will get updated
```
### Test Layout:

This command runs the suite of unit tests in the project

```bash
cd ./src
./dev.(sh/cmd) test # run all unit tests before git commit/push
```

### Configure Runner:

If you want to manually test your runner and run actions from a real repository, you'll have to configure it before running it.

```bash
cd runner/_layout
./config.(sh/cmd) # configure your custom runner
```

You will need your the name of your repository and a runner registration token.
Check [Quickstart](##Quickstart:-Run-a-job-from-a-real-repository) if you don't know how to get this token.

These can also be passed down as arguments to `config.(sh/cmd)`:
```bash
cd runner/_layout
./config.(sh/cmd) --url https://github.com/{your-repo} --token ABCABCABCABCABCABCABCABCABCAB
```

### Run Runner

All that's left to do is to start the runner:
```bash
cd runner/_layout
./run.(sh/cmd) # run your custom runner
```

### View logs:

```bash
cd runner/_layout/_diag
ls
cat (Runner/Worker)_TIMESTAMP.log # view your log file
```

## Editors

[Using Visual Studio Code](https://code.visualstudio.com/)
[Using Visual Studio](https://code.visualstudio.com/docs)  

## Styling

We use the .NET Foundation and CoreCLR style guidelines [located here](
https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md)

### Format C# Code

To format both staged and unstaged .cs files
```
cd ./src
./dev.(cmd|sh) format
```

---

### Dependency Management (`docs/dependency-management.md`)

# Runner Dependency Management Process

## Overview

This document outlines the automated dependency management process for the GitHub Actions Runner, designed to ensure we maintain up-to-date and secure dependencies while providing predictable release cycles.

## Release Schedule

- **Monthly Runner Releases**: New runner versions are released monthly
- **Weekly Dependency Checks**: Automated workflows check for dependency updates every Monday
- **Security Patches**: Critical security vulnerabilities are addressed immediately outside the regular schedule

## Automated Workflows

**Note**: These workflows are implemented across separate PRs for easier review and independent deployment. Each workflow includes comprehensive error handling and security-focused vulnerability detection.

### 1. Foundation Labels

- **Workflow**: `.github/workflows/setup-labels.yml` (PR #4024)
- **Purpose**: Creates consistent dependency labels for all automation workflows
- **Labels**: `dependencies`, `security`, `typescript`, `needs-manual-review`
- **Prerequisite**: Must be merged before other workflows for proper labeling

### 2. Node.js Version Updates

- **Workflow**: `.github/workflows/node-upgrade.yml`
- **Schedule**: Mondays at 6:00 AM UTC
- **Purpose**: Updates Node.js 20 and 24 versions in `src/Misc/externals.sh`
- **Source**: [nodejs.org](https://nodejs.org) and [actions/alpine_nodejs](https://github.com/actions/alpine_nodejs)
- **Priority**: First (NPM depends on current Node.js versions)

### 3. NPM Security Audit

- **Primary Workflow**: `.github/workflows/npm-audit.yml` ("NPM Audit Fix")
  - **Schedule**: Mondays at 7:00 AM UTC
  - **Purpose**: Automated security vulnerability detection and basic fixes
  - **Location**: `src/Misc/expressionFunc/hashFiles/`
  - **Features**: npm audit, security patch application, PR creation
  - **Dependency**: Runs after Node.js updates for optimal compatibility

- **Fallback Workflow**: `.github/workflows/npm-audit-typescript.yml` ("NPM Audit Fix with TypeScript Auto-Fix")
  - **Trigger**: Manual dispatch only
  - **Purpose**: Manual security audit with TypeScript compatibility fixes
  - **Use Case**: When scheduled workflow fails or needs custom intervention
  - **Features**: Enhanced TypeScript auto-repair, graduated security response
  - **How to Use**:
    1. If the scheduled "NPM Audit Fix" workflow fails, go to Actions tab
    2. Select "NPM Audit Fix with TypeScript Auto-Fix" workflow
    3. Click "Run workflow" and optionally specify fix level (auto/manual)
    4. Review the generated PR for TypeScript compatibility issues

### 4. .NET SDK Updates

- **Workflow**: `.github/workflows/dotnet-upgrade.yml`
- **Schedule**: Mondays at midnight UTC
- **Purpose**: Updates .NET SDK and package versions with build validation
- **Features**: Global.json updates, NuGet package management, compatibility checking
- **Independence**: Runs independently of Node.js/NPM updates

### 5. Docker/Buildx Updates

- **Workflow**: `.github/workflows/docker-buildx-upgrade.yml` ("Docker/Buildx Version Upgrade")
- **Schedule**: Mondays at midnight UTC
- **Purpose**: Updates Docker and Docker Buildx versions with multi-platform validation
- **Features**: Container security scanning, multi-architecture build testing
- **Independence**: Runs independently of other dependency updates

### 6. Dependency Monitoring

- **Workflow**: `.github/workflows/dependency-check.yml` ("Dependency Status Check")
- **Schedule**: Mondays at 11:00 AM UTC
- **Purpose**: Comprehensive status report of all dependencies with security audit
- **Features**: Multi-dependency checking, npm audit status, build validation, choice of specific component checks
- **Summary**: Runs last to capture results from all morning dependency updates

## Release Process Integration

### Pre-Release Checklist

Before each monthly runner release:

1. **Check Dependency PRs**:

   ```bash
   # List all open dependency PRs
   gh pr list --label "dependencies" --state open
   
   # List only automated weekly dependency updates
   gh pr list --label "dependencies-weekly-check" --state open
   
   # List only custom dependency automation (not dependabot)
   gh pr list --label "dependencies-not-dependabot" --state open
   ```

2. **Run Manual Dependency Check**:
   - Go to Actions tab → "Dependency Status Check" → "Run workflow"
   - Review the summary for any outdated dependencies

3. **Review and Merge Updates**:
   - Prioritize security-related updates
   - Test dependency updates in development environment
   - Merge approved dependency PRs

### Vulnerability Response

#### Critical Security Vulnerabilities

- **Response Time**: Within 24 hours
- **Process**:
  1. Assess impact on runner security
  2. Create hotfix branch if runner data security is affected
  3. Expedite patch release if necessary
  4. Document in security advisory if applicable

#### Non-Critical Vulnerabilities

- **Response Time**: Next monthly release
- **Process**:
  1. Evaluate if vulnerability affects runner functionality
  2. Include fix in regular dependency update cycle
  3. Document in release notes

## Monitoring and Alerts

### GitHub Actions Workflow Status

- All dependency workflows create PRs with the `dependencies` label
- Failed workflows should be investigated immediately
- Weekly dependency status reports are generated automatically

### Manual Checks

You can manually trigger dependency checks:

- **Full Status**: Run "Dependency Status Check" workflow
- **Specific Component**: Use the dropdown to check individual dependencies

## Dependency Labels

All automated dependency PRs are tagged with labels for easy filtering and management:

### Primary Labels

- **`dependencies`**: All automated dependency-related PRs
- **`dependencies-weekly-check`**: Automated weekly dependency updates from scheduled workflows
- **`dependencies-not-dependabot`**: Custom dependency automation (not created by dependabot)
- **`security`**: Security vulnerability fixes and patches  
- **`typescript`**: TypeScript compatibility and type definition updates
- **`needs-manual-review`**: Complex updates requiring human verification

### Technology-Specific Labels

- **`node`**: Node.js version updates
- **`javascript`**: JavaScript runtime and tooling updates
- **`npm`**: NPM package and security updates
- **`dotnet`**: .NET SDK and NuGet package updates
- **`docker`**: Docker and container tooling updates

### Workflow-Specific Branches

- **Node.js updates**: `chore/update-node` branch
- **NPM security fixes**: `chore/npm-audit-fix-YYYYMMDD` and `chore/npm-audit-fix-with-ts-repair` branches
- **NuGet/.NET updates**: `feature/dotnetsdk-upgrade/{version}` branches
- **Docker updates**: `feature/docker-buildx-upgrade` branch

## Special Considerations

### Node.js Updates

When updating Node.js versions, remember to:

1. Create a corresponding release in [actions/alpine_nodejs](https://github.com/actions/alpine_nodejs)
2. Follow the alpine_nodejs getting started guide
3. Test container builds with new Node versions

### .NET SDK Updates

- Only patch versions are auto-updated within the same major.minor version
- Major/minor version updates require manual review and testing

### Docker Updates

- Updates can include Docker Engine, Docker Buildx, or both
- Verify compatibility with runner container workflows

## Troubleshooting

### Common Issues

1. **NPM Audit Workflow Fails**:
   - Check if `package.json` exists in `src/Misc/expressionFunc/hashFiles/`
   - Verify Node.js setup step succeeded

2. **Version Detection Fails**:
   - Check if upstream APIs are available
   - Verify parsing logic for version extraction

3. **PR Creation Fails**:
   - Ensure `GITHUB_TOKEN` has sufficient permissions
   - Check if branch already exists

### Contact

For questions about the dependency management process:

- Create an issue with the `dependencies` label
- Review existing dependency management workflows
- Consult the runner team for security-related concerns

## Metrics and KPIs

Track these metrics to measure dependency management effectiveness:

- Number of open dependency PRs at release time
- Time to merge dependency updates
- Number of security vulnerabilities by severity
- Release cycle adherence (monthly target)

---

### Adrs 0263 Proxy Support (`docs/adrs/0263-proxy-support.md`)

# ADR 263: Self-Hosted Runner Proxies

**Date**: 2019-11-13

**Status**: Accepted

## Context

- Proxy support is required for some enterprises and organizations to start using their own self-hosted runners
- While there is not a standard convention, many applications support setting proxies via the environment variables `http_proxy`, `https_proxy`, `no_proxy`, such as curl, wget, perl, python, docker, git, and R
  - Some of these applications use `HTTPS_PROXY` versus `https_proxy`, but most understand or primarily support the lowercase variant

## Decision

We will update the Runner to use the conventional environment variables for proxies: `http_proxy`, `https_proxy`, and `no_proxy` if they are set.
These are described in detail below:
- `https_proxy` a proxy URL for all https traffic. It may contain basic authentication credentials. For example:
  - http://proxy.com
  - http://127.0.0.1:8080
  - http://user:password@proxy.com
- `http_proxy` a proxy URL for all http traffic. It may contain basic authentication credentials. For example:
  - http://proxy.com
  - http://127.0.0.1:8080
  - http://user:password@proxy.com
- `no_proxy` a comma-separated list of hosts that should not use the proxy. An optional port may be specified. For example:
  - `google.com`
  - `yahoo.com:443`
  - `google.com,bing.com`

We won't use `http_proxy` for https traffic when `https_proxy` is not set, this behavior lines up with any libcurl based tools (curl, git) and wget.
Otherwise, action authors and workflow users need to adjust to differences between the runner proxy convention, and tools used by their actions and scripts.  

Example:  
  Customer sets `http_proxy=http://127.0.0.1:8888` and configures the runner against `https://github.com/owner/repo`, with the `https_proxy` -> `http_proxy` fallback, the runner will connect to the server without any problem. However, if a user runs `git push` to `https://github.com/owner/repo`, `git` won't use the proxy since it requires `https_proxy` to be set for any https traffic.

> `golang`, `node.js`, and other dev tools from the Linux community use `http_proxy` for both http and https traffic based on my research.

A majority of our users are using Linux where these variables are commonly required to be set by various programs. By reading these values, we simplify the process for self-hosted runners to set up a proxy and expose it in a way users are already familiar with.

A password provided for a proxy will be masked in the logs.

We will support the lowercase and uppercase variants, with lowercase taking priority if both are set.

### No Proxy Format

While exact implementations are different per application on handling `no_proxy` env, most applications accept a comma-separated list of hosts. Some accept wildcard characters (`*`). We are going to do exact case-insensitive matches, and not support wildcards at this time.
For example:
- `example.com` will match `example.com`, `foo.example.com`, and `foo.bar.example.com`
- `foo.example.com` will match `bar.foo.example.com` and `foo.example.com`

We will not support IP addresses for `no_proxy`, only hostnames.

## Consequences

1. Enterprises and organizations needing proxy support will be able to embrace self-hosted runners
2. Users will need to set these environment variables before configuring the runner in order to use a proxy when configuring
3. The runner will read from the environment variables during config and runtime and use the provided proxy if it exists
4. Users may need to pass these environment variables into other applications if they do not natively take these variables
5. Action authors may need to update their workflows to react to these environment variables
6. We will document the way of setting environment variables for runners using the environment variables and how the runner uses them
7. Like all other secrets, users will be able to relatively easily figure out the proxy password if they can modify a workflow file running on a self-hosted machine

---

### Adrs 0274 Step Outcome And Conclusion (`docs/adrs/0274-step-outcome-and-conclusion.md`)

# ADR 0274: Step outcome and conclusion

**Date**: 2020-01-13

**Status**: Accepted

## Context

This ADR proposes adding `steps.<id>.outcome` and `steps.<id>.conclusion` to the steps context.

This allows downstream a step to run based on whether a previous step succeeded or failed.

Reminder, currently the steps contains `steps.<id>.outputs`.

## Decision

For steps that have completed, populate `steps.<id>.outcome` and `steps.<id>.conclusion` with one of the following values:

- `success`
- `failure`
- `cancelled`
- `skipped`

When a continue-on-error step fails, the outcome will be `failure` even though the final conclusion is `success`.

### Example

```yaml
steps:

  - id: experimental
    continue-on-error: true
    run: ./build.sh experimental

  - if: ${{ steps.experimental.outcome == 'success' }}
    run: ./publish.sh experimental
```

### Terminology

The runs API uses the term `conclusion`.

Therefore we use a different term `outcome` for the value prior to continue-on-error.

The following is a snippet from the runs API response payload:

```json
      "steps": [
        {
          "name": "Set up job",
          "status": "completed",
          "conclusion": "success",
          "number": 1,
          "started_at": "2020-01-09T11:06:16.000-05:00",
          "completed_at": "2020-01-09T11:06:18.000-05:00"
        },
```

## Consequences

- Update runner
- Update [docs](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#steps-context)

---

### Adrs 0276 Problem Matchers (`docs/adrs/0276-problem-matchers.md`)

# ADR 0276: Problem Matchers

**Date** 2019-06-05

**Status** Accepted

## Context

Compilation failures during a CI build should surface good error messages.

For example, the actual compile errors from the typescript compiler should bubble as issues in the UI. And not simply "tsc exited with exit code 1".

VSCode has an extensible model for solving this type of problem. VSCode allows users to configure which [problems matchers](https://code.visualstudio.com/docs/editor/tasks#_defining-a-problem-matcher) to use, when scanning output. For example, a user can apply the `tsc` problem matcher to receive a rich error output experience in VSCode, when compiling their typescript project.

The problem-matcher concept fits well with "setup" actions. For example, the `setup-nodejs` action will download node.js, add it to the PATH, and register the `tsc` problem matcher. For the duration of the job, the `tsc` problem matcher will be applied against the output.

## Decision

### Registration

#### Using `::` command

`::add-matcher::path-to-problem-matcher-config.json`

Using a `::` command allows for flexibility:
- Ad hoc scripts can register problem matchers
- Allows problem matchers to be conditionally registered

Note, if a matcher with the same name is registered a second time, it will clobber the first instance.

Note, at some point the syntax changed from `##` to `::`.

#### Unregister using `::` command

A way out for rare cases where scoping is a problem.

`::remove-matcher::owner`

For this to be usable, the `owner` needs to be discoverable. Therefore, debug print the owner on registration.

### Single line matcher

Consider the output:

```
[...]

Build FAILED.

"C:\temp\problemmatcher\myproject\ConsoleApp1\ConsoleApp1.sln" (default target) (1) ->
"C:\temp\problemmatcher\myproject\ConsoleApp1\ConsoleApp1\ConsoleApp1.csproj" (default target) (2) ->
"C:\temp\problemmatcher\myproject\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj" (default target) (3) ->
(CoreCompile target) -> 
  Class1.cs(16,24): warning CS0612: 'ClassLibrary1.Helpers.MyHelper.Name' is obsolete [C:\temp\problemmatcher\myproject\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj]


"C:\temp\problemmatcher\myproject\ConsoleApp1\ConsoleApp1.sln" (default target) (1) ->
"C:\temp\problemmatcher\myproject\ConsoleApp1\ConsoleApp1\ConsoleApp1.csproj" (default target) (2) ->
"C:\temp\problemmatcher\myproject\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj" (default target) (3) ->
(CoreCompile target) -> 
  Helpers\MyHelper.cs(16,30): error CS1002: ; expected [C:\temp\problemmatcher\myproject\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj]

    1 Warning(s)
    1 Error(s)
```

The below match configuration uses a regular expression to discover problem lines. And the match groups are mapped into issue-properties.

```json
"owner": "msbuild",
"pattern": [
  {
    "regexp": "^\\s*([^:]+)\\((\\d+),(\\d+)\\): (error|warning) ([^:]+): (.*) \\[(.+)\\]$",
    "file": 1,
    "line": 2,
    "column": 3,
    "severity": 4,
    "code": 5,
    "message": 6,
    "fromPath": 7
  }
]
```

The above output and match configuration produces the following matches:

```
line:     Class1.cs(16,24): warning CS0612: 'ClassLibrary1.Helpers.MyHelper.Name' is obsolete [C:\myrepo\myproject\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj]
file:     Class1.cs
line:     16
column:   24
severity: warning
code:     CS0612
message:  'ClassLibrary1.Helpers.MyHelper.Name' is obsolete
fromPath: C:\myrepo\myproject\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj
```

```
line:     Helpers\MyHelper.cs(16,30): error CS1002: ; expected [C:\myrepo\myproject\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj]
file:     Helpers\MyHelper.cs
line:     16
column:   30
severity: error
code:     CS1002
message:  ; expected
fromPath: C:\myrepo\myproject\ConsoleApp1\ClassLibrary1\ClassLibrary1.csproj
```

Additionally the line will appear red in the web UI (prefix with `::error`).

Note, an error does not imply task failure. Exit codes communicate failure.

Note, strip color codes when evaluating regular expressions.

### Multi-line matcher

Consider the below output from ESLint in stylish mode. The file name is printed once, yet multiple error lines are printed.

```
test.js
  1:0   error  Missing "use strict" statement                 strict
  5:10  error  'addOne' is defined but never used             no-unused-vars
✖ 2 problems (2 errors, 0 warnings)
```

The below match configuration uses multiple regular expressions, for the multiple lines.

And the last pattern of a multiline matcher can specify the `loop` property. This allows multiple errors to be discovered.

```json
"owner": "eslint-stylish",
"pattern": [
  {
    "regexp": "^([^\\s].*)$",
    "file": 1
  },
  {
    "regexp": "^\\s+(\\d+):(\\d+)\\s+(error|warning|info)\\s+(.*)\\s\\s+(.*)$",
    "line": 1,
    "column": 2,
    "severity": 3,
    "message": 4,
    "code": 5,
    "loop": true
  }
]
```

The above output and match configuration produces two matches:

```
line:     1:0   error  Missing "use strict" statement                 strict
file:     test.js
line:     1
column:   0
severity: error
message:  Missing "use strict" statement
code:     strict
```

```
line:     5:10  error  'addOne' is defined but never used             no-unused-vars
file:     test.js
line:     5
column:   10
severity: error
message:  'addOne' is defined but never used
code:     no-unused-vars
```

Note, in the above example only the error line will appear red in the web UI. The \"file\" line will not appear red.

### Other details

#### Configuration `owner`

Can be used to stomp over or remove.

#### Rooting the file

The goal of the file information is to provide a hyperlink in the UI.

Solving this problem means:
- Rooting the file when unrooted:
  - Use the `fromPath` if specified (assume file path)
  - Use the `github.workspace` (where the repo is cloned on disk)
- Match against a repository to determine the relative path within the repo

This is a place where we diverge from VSCode. VSCode task configurations are specific to the local workspace (workspace root is known or can be specified). We're solving a more generic problem, so we need more information - specifically the `fromPath` property - in order to accurately root the path.

In order to avoid creating inaccurate hyperlinks on the error issues, the agent will verify the file exists and is in the main repository. Otherwise omit the file property from the error issue and debug trace what happened.

#### Supported severity levels

Ordinal ignore case:

- `warning`
- `error`

Coalesce empty with \"error\". For any other values, omit logging an issue and debug trace what happened.

#### Default severity level

Problem matchers are unable to interpret severity strings other than `warning` and `error`. The `severity` match group expects `warning` or `error` (case insensitive).

However some tools indicate error/warning in different ways. For example `flake8` uses codes like `E100`, `W200`, and `F300` (error, warning, fatal, respectively).

Therefore, allow a property `severity`, sibling to `owner`, which identifies the default severity for the problem matcher. This allows two problem matchers to be registered - one for warnings and one for errors.

For example, given the following `flake8` output:

```
./bootcamp/settings.py:156:80: E501 line too long (94 > 79 characters)
./bootcamp/settings.py:165:5: F403 'from local_settings import *' used; unable to detect undefined names
```

Two problem matchers can be used:

```json
{
    "problemMatcher": [
        {
            "owner": "flake8",
            "pattern": [
                {
                    "regexp": "^(.+):(\\d+):(\\d+): ([EF]\\d+) (.+)$",
                    "file": 1,
                    "line": 2,
                    "column": 3,
                    "code": 4,
                    "message": 5
                }
            ]
        },
        {
            "owner": "flake8-warnings",
            "severity": "warning",
            "pattern": [
                {
                    "regexp": "^(.+):(\\d+):(\\d+): (W\\d+) (.+)$",
                    "file": 1,
                    "line": 2,
                    "column": 3,
                    "code": 4,
                    "message": 5
                }
            ]
        }
    ]
}
```

#### Default from path

The problem matcher can specify a `fromPath` property at the top level, which applies when a specific pattern doesn't provide a value for `fromPath`. This is useful for tools that don't include project file information in their output.

For example, given the following compiler output that doesn't include project file information:

```
ClassLibrary.cs(16,24): warning CS0612: 'ClassLibrary.Helpers.MyHelper.Name' is obsolete
```

A problem matcher with a default from path can be used:

```json
{
    "problemMatcher": [
        {
            "owner": "csc-minimal",
            "fromPath": "ClassLibrary/ClassLibrary.csproj",
            "pattern": [
                {
                    "regexp": "^(.+)\\((\\d+),(\\d+)\\): (error|warning) (.+): (.*)$",
                    "file": 1,
                    "line": 2,
                    "column": 3,
                    "severity": 4,
                    "code": 5,
                    "message": 6
                }
            ]
        }
    ]
}
```

This ensures that the file is rooted to the correct path when there's not enough information in the error messages to extract a `fromPath`.

#### Mitigate regular expression denial of service (ReDos)

If a matcher exceeds a 1 second timeout when processing a line, retry up to two three times total.
After three unsuccessful attempts, warn and eject the matcher. The matcher will not run again for the duration of the job.

### Where we diverge from VSCode

- We added the `fromPath` concept for rooting paths. This is done differently in VSCode, since a task is the scope (root path well known). For us, the job is the scope.
- VSCode allows additional activation info background tasks that are always running (recompile on files changed). They allow regular expressions to define when the matcher scope begins and ends. This is an interesting concept that we could leverage to help solve our scoping problem.

## Consequences

- Setup actions should register problem matchers

---

### Adrs 0277 Run Action Shell Options (`docs/adrs/0277-run-action-shell-options.md`)

# ADR 0277: Run action shell option

**Date** 2019-07-09

**Status** Accepted

## Context
run-actions run scripts using a platform specific shell:
`bash -eo pipefail` on non-windows, and `cmd.exe /c /d /s` on windows

The `shell` option overrides this to allow different flags or completely different shells/interpreters

A small example is:
```yml
jobs:
  bash-job:
    actions:
    - run: echo "Hello"
      shell: bash
  python-job:
    actions:
    - run: print("Hello")
      shell: python {0}
```

## Decision

___

### Shell option
The keyword being used is `shell` 

`shell` can be either:

1. Builtins / Explicitly supported keywords. It is useful to support at least `cmd`, and `powershell` on Windows. Because `cmd my_cmd_script` and `powershell my_ps1_script` are not valid the same way many Linux/cross-platform interpreters are, e.g. `bash myscript` or `python myscript`. Those tools (and potentially others) also require the correct file extension to run, or must be run in a particular way to get the exit codes consistently, so we must have first class knowledge about them. We provide default templates for these keywords as follows:
    - `cmd`: Default is: `%ComSpec% /D /E:ON /V:OFF /S /C "CALL "{0}""` where the script name is automatically appended with `.cmd` and substituted for `{0}`
        - Note this is equivalent to the default Windows behavior if no shell option is given
    - `pwsh`: Default is: `pwsh -command "& '{0}'"` where the script is automatically appended with `.ps1`
    - `powershell`: Default is: `powershell -command "& '{0}'"` where the script is automatically appended with `.ps1`
    - `bash`: Uses `bash --noprofile --norc -eo pipefail {0}`
        - The default behavior on non-Windows if no shell is given is to attempt this first
    - `sh`: Uses `sh -e {0}`
        - This is the default behavior on non-Windows if no shell is given, AND `bash` (see above) was not located on the PATH
    - `python`: `python {0}`
    - **NOTE**: The exact command ran may vary by machine. We only provide default arguments and command format for the listed shell. While the above behavior is expected on hosted machines, private runners may vary. For example, `sh` (or other commands) may actually be a link to `/bin/dash`, `/bin/bash`, or other

1. A template string: `command [...options] {0} [...more_options]`
    - As above, the file name of the temporary script will be templated in. This gives users more control to have options at any location relative to the script path
    - The first whitespace-delimited word of the string will be interpreted as the command
    - e.g. `python {0} arg1 arg2` or similar can be used if passing args is needed. Some shells will require other options after the filename for various reasons

Note that (1) simply provides defaults that are executed with the same mechanism as (2). That is:
- A temporary script file is generated, and the path to that file is templated into the string at `{0}`
- The first word of the formatted string is assumed to be a command, and we attempt to locate its full path
- The fully qualified path to the command, plus the remaining arguments, is executed
    - e.g. `shell: bash` expands to `/bin/bash --noprofile --norc -eo pipefail /runner/_layout/_work/_temp/f8d4fb2b-19d9-47e6-a786-4cc538d52761.sh` on my private runner

At this time, **THE LIST OF WELL-KNOWN SHELL OPTIONS IS**:
- cmd - Windows (hosted vs2017, vs2019) only
- powershell - Windows (hosted vs2017, vs2019) only
- sh - All hosted platforms
- pwsh - All hosted platforms
- bash - All hosted platforms
- python - All hosted platforms. Can use setup-python to configure which python will be used
___

### Containers
For container jobs, `shell` should just work the same as above, transparently. We will simply `exec` the command in the job container, passing the same arguments in

___

### Exit codes / Error action preference

For builtin shells, we provide defaults that make the most sense for CI, running within Actions, and being executed by our runner

bash/sh:
- Fail-fast behavior using `set -e o pipefail` is the default for `bash` and `shell` builtins, and by default when no option is given on non-Windows platforms
- Users can opt out of fail-fast and take full control easily by providing a template string to the shell options, eg: `bash {0}`.
- sh-like shells exit with the exit code of the last command executed in a script, and is our default behavior. Thus the runner reports the status of the step as fail/succeed based on this exit code

powershell/pwsh
- Fail-fast behavior when possible. For `pwsh` and `powershell` builtins, we will prepend `$ErrorActionPreference = 'stop'` to script contents
- We append `if ((Test-Path -LiteralPath variable:\LASTEXITCODE)) { exit $LASTEXITCODE }` to powershell scripts to get Action statuses to reflect the script's last exit code
- Users can always opt out by not using the builtins, and providing a shell option like: `pwsh -File {0}`, or `powershell -Command "& '{0}'"`, depending on need

cmd
- There doesn't seem to be a way to fully opt in to fail-fast behavior other than writing your script to check each error code and respond accordingly, so we can't actually provide that behavior by default, it will be completely up to the user to write this behavior into their script
- cmd.exe will exit (return the error code to the runner) with the errorlevel of the last program it executed. This is internally consistent with the previous default behavior (sh, pwsh) and is the cmd.exe default, so we keep that behavior

## Consequences
Valid `shell` options will depend on the hosted images. We will need to maintain tight image compat

First class support for a shell will require a major version schema change to modify. We cannot remove or modify the behavior of a well-known supported option, However, adding first class support for new shells is backwards compatible. For instance, we can add a well-known `python` option, because non-well-known options would have always needed to include `{0}`, e.g. `python {0}`

---

### Adrs 0278 Env Context (`docs/adrs/0278-env-context.md`)

# ADR 0278: Env Context

**Date**: 2019-09-30

**Status**: Accepted

## Context

User wants to reference workflow variables defined in workflow yaml file for action's input, displayName and condition.  

## Decision

### Add `env` context in the runner

Runner will create and populate the `env` context for every job execution using following logic:
1. On job start, create `env` context with any environment variables in the job message, these are env defined in customer's YAML file's job/workflow level `env` section.
2. Update `env` context when customer use `::set-env::` to set env at the runner level.
3. Update `env` context with step's `env` block before each step runs.

The `env` context is only available in the runner, customer can't use the `env` context in any server evaluation part, just like the `runner` context

Example yaml:
```yaml

env:
  env1: 10
  env2: 20
  env3: 30
jobs:
  build:
    env:
      env1: 100
      env2: 200
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo ${{ env.env1 }}  // 1000
          echo $env1            // 1000
          echo $env2            // 200
          echo $env3            // 30
        if: env.env2 == 200     // true
        name: ${{ env.env1 }}_${{ env.env2 }}   //1000_200
        env:
          env1: 1000
```

### Don't populate the `env` context with environment variables from runner machine. 

With job container and container action, the `env` context may not have the right value customer want and will cause confusion.  
Ex:
```yaml
build:
  runs-on: ubuntu-latest   <- $USER=runner in hosted machine
  container: ubuntu:16.04   <- $USER=root in container
  steps:
  - run: echo ${{env.USER}}    <- what should customer expect this output?  runner/root
  - uses: docker://ubuntu:18.04
    with:
      args: echo ${{env.USER}}  <- what should customer expect this output? runner/root
```

---

### Adrs 0279 HashFiles Expression Function (`docs/adrs/0279-hashFiles-expression-function.md`)

# ADR 0279: HashFiles Expression Function

**Date**: 2019-09-30

**Status**: Accepted

## Context
First party action `actions/cache` needs a input which is an explicit `key` used for restoring and saving the cache. For packages caching, the most common `key` might be the hash result of contents from all `package-lock.json` under `node_modules` folder.
  
There are serval different ways to get the hash `key` input for `actions/cache` action.

1. Customer calculate the `key` themselves from a different action, customer won't like this since it needs extra step for using cache feature
```yaml
  steps:
  - run: |
      hash=some_linux_hash_method(file1, file2, file3)
      echo ::set-output name=hash::$hash
    id: createHash
  - uses: actions/cache@v1
    with:
      key: ${{ steps.createHash.outputs.hash }}
``` 

2. Make the `key` input of `actions/cache` follow certain convention to calculate hash, this limited the `key` input to a certain format customer may not want.
```yaml
  steps:
  - uses: actions/cache@v1
    with:
      key: ${{ runner.os }}|${{ github.workspace }}|**/package-lock.json
```

## Decision

### Add hashFiles() function to expression engine for calculate files' hash

`hashFiles()` will only allow on runner side since it needs to read files on disk, using `hashFiles()` on any server side evaluated expression will cause runtime errors.

`hashFiles()` will only support hashing files under the `$GITHUB_WORKSPACE` since the expression evaluated on the runner, if customer use job container or container action, the runner won't have access to file system inside the container.

`hashFiles()` will only take 1 parameters:
 - `hashFiles('**/package-lock.json')`  // Search files under `$GITHUB_WORKSPACE` and calculate a hash for them

**Question: Do we need to support more than one match patterns?**  
Ex: `hashFiles('**/package-lock.json', '!toolkit/core/package-lock.json', '!toolkit/io/package-lock.json')`  
Answer: Only support single match pattern for GA, we can always add later.

This will help customer has better experience with the `actions/cache` action's input.
```yaml
  steps:
  - uses: actions/cache@v1
    with:
      key: ${{hashFiles('**/package-lock.json')}}-${{github.ref}}-${{runner.os}}
```

For search pattern, we will use basic globbing (`*`, `?`, and `[]`) and globstar (`**`).

Additional pattern details:
- Root relative paths with `github.workspace` (the main repo)
- Make `*` match files that start with `.`
- Case insensitive on Windows
- Accept `\` or `/` path separators on Windows

Hashing logic:
1. Get all files under `$GITHUB_WORKSPACE`.
2. Use search pattern filter all files to get files that matches the search pattern. (search pattern only apply to file path not folder path)
3. Sort all matched files by full file path in alphabet order.
4. Use SHA256 algorithm to hash each matched file and store hash result.
5. Use SHA256 to hash all stored files' hash results to get the final 64 chars hash result.

**Question: Should we include the folder structure info into the hash?**  
Answer: No

---

### Adrs 0280 Command Input Echoing (`docs/adrs/0280-command-input-echoing.md`)

# ADR 0280: Echoing of Command Input

**Date**: 2019-11-04  

**Status**: Accepted

## Context

Command echoing as a default behavior tends to clutter the user logs, so we want to swap to a system where users have to opt in to see this information.

Command outputs will still be echoed in the case there are any errors processing such commands. This is so the end user can have more context on why the command failed and help with troubleshooting.

Echo output in the user logs can be explicitly controlled by the new commands `::echo::on` and `::echo::off`. By default, echoing is enabled if `ACTIONS_STEP_DEBUG` secret is enabled, otherwise echoing is disabled.

## Decision
- The only commands that currently echo output are
  - `remove-matcher`
  - `add-matcher`
  - `add-path`
- These will no longer echo the command, if processed successfully
- All commands echo the input when any of these conditions is fulfilled:
  1. When such commands fail with an error
  2. When `::echo::on` is set
  3. When the `ACTIONS_STEP_DEBUG` is set, and echoing hasn't been explicitly disabled with `::echo::off`
- There are a few commands that won't be echoed, even when echo is enabled. These are (as of 2019/11/04):
   - `add-mask`
   - `debug`
   - `warning`
   - `error`
- The three commands above will not echo, either because echoing the command would leak secrets (e.g. `add-mask`), or it would not add any additional troubleshooting information to the logs (e.g. `debug`). It's expected that future commands would follow these "echo-suppressing" guidelines as well. Echo-suppressed commands are still free to output other information to the logs, as deemed fit.

---

### Adrs 0297 Base64 Masking Trailing Characters (`docs/adrs/0297-base64-masking-trailing-characters.md`)

# ADR 0297: Base64 Masking Trailing Characters

**Date** 2020-01-21

**Status** Proposed

## Context

The Runner registers a number of Value Encoders, which mask various encodings of a provided secret. Currently, we register a 3 base64 Encoders:
- The base64 encoded secret
- The secret with the first character removed then base64 encoded
- The secret with the first two characters removed then base64 encoded

This gives us good coverage across the board for secrets and secrets with a prefix (i.e. `base64($user:$pass)`).

However, we don't have great coverage for cases where the secret has a string appended to it before it is base64 encoded (i.e.: `base64($pass\n))`). 

Most notably we've seen this as a result of user error where a user accidentally appends a newline or space character before encoding their secret in base64.

## Decision

### Trim end characters

We are going to modify all existing base64 encoders to trim information before registering as a secret.
We will trim:
- `=` from the end of all base64 strings. This is a padding character that contains no information. 
  - Based on the number of `=`'s at the end of a base64 string, a malicious user could predict the length of the original secret modulo 3. 
    - If a user saw `***==`, they would know the secret could be 1,4,7,10... characters.
- If a string contains `=` we will also trim the last non-padding character from the base64 secret.
  - This character can change if a string is appended to the secret before the encoding.


### Register a fourth encoder

We will also add back in the original base64 encoded secret encoder for four total encoders:
- The base64 encoded secret
- The base64 encoded secret trimmed
- The secret with the first character removed then base64 encoded and trimmed
- The secret with the first two characters removed then base64 encoded and trimmed

This allows us to fully cover the most common scenario where a user base64 encodes their secret and expects the entire thing to be masked.
This will result in us only revealing length or bit information when a prefix or suffix is added to a secret before encoding. 

## Consequences

- In the case where a secret has a prefix or suffix added before base64 encoding, we may now reveal up to 20 bits of information and the length of the original string modulo 3, rather then the original 16 bits and no length information
- Secrets with a suffix appended before encoding will now be masked across the board. Previously it was only masked if it was a multiple of 3 characters
- Performance will suffer in a negligible way

---

### Adrs 0354 Runner Machine Info (`docs/adrs/0354-runner-machine-info.md`)

# ADR 354: Expose runner machine info

**Date**: 2020-03-02

**Status**: Pending

## Context

- Provide a mechanism in the runner to include extra information in `Set up job` step's log.
  Ex: Include OS/Software info from Hosted image.

## Decision

The runner will look for a file `.setup_info` under the runner's root directory, The file can be a JSON with a simple schema.
```json
[
  {
    "group": "OS Detail",
    "detail": "........"
  },
  {
    "group": "Software Detail",
    "detail": "........"
  }
]
```
The runner will use `::group` and `::endgroup` to fold all detail info into an expandable group.

Both [virtual-environments](https://github.com/actions/virtual-environments) and self-hosted runners can use this mechanism to add extra logging info to the `Set up job` step's log.

## Consequences

1. Change the runner to best effort read/parse `.extra_setup_info` file under runner root directory.
2. [virtual-environments](https://github.com/actions/virtual-environments) generate the file during image generation.
3. Change MMS provisioner to properly copy the file to runner root directory at runtime.

---

### Adrs 0361 Wrapper Action (`docs/adrs/0361-wrapper-action.md`)

# ADR 361: Wrapper Action

**Date**: 2020-03-06

**Status**: Pending

## Context

In addition to action's regular execution, action author may wants their action to have a chance to participate in:
- Job initialization
  My Action will collect machine resource usage (CPU/RAM/Disk) during a workflow job execution, we need to start perf recorder at the beginning of the job.
- Job cleanup
  My Action will dirty local workspace or machine environment during execution, we need to cleanup these changes at the end of the job.  
  Ex: `actions/checkout@v2` will write `github.token` into local `.git/config` during execution, it has post job cleanup defined to undo the changes.

## Decision

### Add `pre` and `post` execution to action

Node Action Example:

```yaml
 name: 'My action with pre'
 description: 'My action with pre'
 runs:
   using: 'node12'
   pre: 'setup.js'
   pre-if: 'success()' // Optional
   main: 'index.js'
   post: 'cleanup.js'
   post-if: 'success()' // Optional
```

Container Action Example:

```yaml
 name: 'My action with pre'
 description: 'My action with pre'
 runs:
   using: 'docker'
   image: 'mycontainer:latest'
   pre-entrypoint: 'setup.sh'
   pre-if: 'success()' // Optional
   entrypoint: 'entrypoint.sh'
   post-entrypoint: 'cleanup.sh'
   post-if: 'success()' // Optional
```

Both `pre` and `post` will have default `pre-if/post-if` set to `always()`.  
Setting `pre` to `always()` will make sure no matter what condition evaluate result the `main` gets at runtime, the `pre` has always run already.   
`pre` executes in order of how the steps are defined.  
`pre` will always be added to job steps list during job setup.  
> Action referenced from local repository (`./my-action`) won't get `pre` setup correctly since the repository haven't checked-out during job initialization.
> We can't use GitHub api to download the repository since there is about a 3 minute delay between `git push` and the new commit available to download using GitHub api.

`post` will be pushed into a `poststeps` stack lazily when the action's `pre` or `main` execution passed `if` condition check and about to run, you can't have an action that only contains a `post`, we will pop and run each `post` after all `pre` and `main` finished.
> Currently `post` works for both repository action (`org/repo@v1`) and local action (`./my-action`)

Valid action:
- only has `main`
- has `pre` and `main`
- has `main` and `post`
- has `pre`, `main`, and `post`

Invalid action:
- only has `pre`
- only has `post`
- has `pre` and `post`

Potential downside of introducing `pre`:

- Extra magic wrt step order. Users should control the step order. Especially when we introduce templates.  
- Eliminates the possibility to lazily download the action tarball, since `pre` always run by default, we have to download the tarball to check whether action defined a `pre`  
- `pre` doesn't work with local action, we suggested customer use local action for testing their action changes, ex CI for their action, to avoid delay between `git push` and GitHub repo tarball download api.  
- Condition on the `pre` can't be controlled using dynamic step outputs. `pre` executes too early.

---

### Adrs 0397 Runner Registration Labels (`docs/adrs/0397-runner-registration-labels.md`)

# ADR 0397: Support adding custom labels during runner config
**Date**: 2020-03-30

**Status**: Approved

## Context

Since configuring self-hosted runners is commonly automated via scripts, the labels need to be able to be created during configuration.  The runner currently registers the built-in labels (os, arch) during registration but does not accept labels via command line args to extend the set registered.

See Issue: https://github.com/actions/runner/issues/262

This is another version of [ADR275](https://github.com/actions/runner/pull/275)

## Decision

This ADR proposes that we add a `--labels` option to the `config`, which could be used to add custom additional labels to the configured runner.

For example, to add a single additional label the operator could run:
```bash
./config.sh --labels mylabel
```
> Note: the current runner command line parsing and envvar override algorithm only support a single argument (key).

This would add the label `mylabel` to the runner, and enable users to select the runner in their workflow using this label:
```yaml
runs-on: [self-hosted, mylabel]
```

To add multiple labels the operator could run:
```bash
./config.sh --labels mylabel,anotherlabel
```
> Note: the current runner command line parsing and envvar override algorithm only supports a single argument (key).

This would add the label `mylabel` and `anotherlabel` to the runner, and enable users to select the runner in their workflow using this label:
```yaml
runs-on: [self-hosted, mylabel, anotherlabel]
```

It would not be possible to remove labels from an existing runner using `config.sh`, instead labels would have to be removed using the GitHub UI.

The labels argument will split on commas, trim and discard empty strings.  That effectively means don't use commas in unattended config label names.  Alternatively, we could choose to escape commas but it's a nice to have.

## Replace

If an existing runner exists and the option to replace is chosen (interactively or via unattended as in this scenario), then the labels will be replaced/overwritten (not merged).

## Overriding built-in labels

Note that it is possible to register "built-in" hosted labels like `ubuntu-latest` and is not considered an error.  This is an effective way for the org/runner admin to dictate by policy through registration that this set of runners will be used without having to edit all the workflow files now and in the future.

We will also not make other restrictions such as limiting explicitly adding os/arch labels and validating.  We will assume that explicit labels were added for a reason and not restricting offers the most flexibility and future-proofing / compatibility.

## Consequences

The ability to add custom labels to a self-hosted runner would enable most scenarios where job runner selection based on runner capabilities or characteristics are required.

---

### Adrs 0549 Composite Run Steps (`docs/adrs/0549-composite-run-steps.md`)

# ADR 0549: Composite Run Steps

**Date**: 2020-06-17

**Status**: Accepted

## Context

Customers want to be able to compose actions from actions (ex: https://github.com/actions/runner/issues/438)

An important step towards meeting this goal is to build functionality for actions where users can simply execute any number of steps. 

### Guiding Principles

We don't want the workflow author to need to know how the internal workings of the action work. Users shouldn't know the internal workings of the composite action (for example, `default.shell` and `default.workingDir` should not be inherited from the workflow file to the action file). When deciding how to design certain parts of composite run steps, we want to treat it as one logical step for the consumer.

A composite action is treated as **one** individual job step (this is known as encapsulation).

## Decision

**In this ADR, we only support running multiple run steps in an Action.** In doing so, we build in support for mapping and flowing the inputs, outputs, and env variables (ex: All nested steps should have access to their parents' input variables and nested steps can overwrite the input variables).

### Composite Run Steps Features
This feature supports at the top action level:
- name
- description
- inputs
- runs
- outputs

This feature supports at the run step level:
- name
- id
- run
- env
- shell
- working-directory

This feature **does not support** at the run step level:
- timeout-minutes
- secrets
- conditionals (needs, if, etc.)
- continue-on-error

### Steps

Example `workflow.yml`

```yaml
jobs:
  build:
    runs-on: self-hosted
    steps:
    - id: step1
      uses: actions/setup-python@v1
    - id: step2
      uses: actions/setup-node@v2
    - uses: actions/checkout@v2
    - uses: user/composite@v1
    - name: workflow step 1
      run: echo hello world 3
    - name: workflow step 2
      run: echo hello world 4
```

Example `user/composite/action.yml`

```yaml
runs:
  using: "composite"
  steps:
    - run: pip install -r requirements.txt
      shell: bash
    - run: npm install
      shell: bash
```

Example Output

```yaml
[npm installation output]
[pip requirements output]
echo hello world 3
echo hello world 4
```

We add a token called "composite" which allows our Runner code to process composite actions. By invoking "using: composite", our Runner code then processes the "steps" attribute, converts this template code to a list of steps, and finally runs each run step sequentially. If any step fails and there are no `if` conditions defined, the whole composite action job fails. 

### Defaults

We will not support "defaults" in a composite action. 

### Shell and Working-directory

For each run step in a composite action, the action author can set the `shell` and `working-directory` attributes for that step. The shell attribute is **required** for each run step because the action author does not know what the workflow author is using for the operating system so we need to explicitly prevent unknown behavior by making sure that each run step has an explicit shell **set by the action author.** On the other hand, `working-directory` is optional. Moreover, the composite action author can map in values from the `inputs` for its `shell` and `working-directory` attributes at the step level for an action. 

For example,

`action.yml`


```yaml
inputs:
  shell_1:
    description: 'Your name'
    default: 'pwsh'
steps:
  - run: echo 1
    shell: ${{ inputs.shell_1 }}
```

Note, the workflow file and action file are treated as separate entities. **So, the workflow `defaults` will never change the `shell` and `working-directory` value in the run steps in a composite action.** Note, `defaults` in a workflow only apply to run steps not "uses" steps (steps that use an action).

### Running Local Scripts

Example 'workflow.yml':
```yaml
jobs:
  build:
    runs-on: self-hosted
    steps:
    - uses: user/composite@v1
```

Example `user/composite/action.yml`:

```yaml
runs:
  using: "composite"
  steps: 
    - run: chmod +x ${{ github.action_path }}/test/script2.sh
      shell: bash
    - run: chmod +x $GITHUB_ACTION_PATH/script.sh
      shell: bash
    - run: ${{ github.action_path }}/test/script2.sh
      shell: bash
    - run: $GITHUB_ACTION_PATH/script.sh
      shell: bash
```
Where `user/composite` has the file structure:
```
.
+-- action.yml
+-- script.sh
+-- test
|   +-- script2.sh
```


Users will be able to run scripts located in their action folder by first prepending the relative path and script name with `$GITHUB_ACTION_PATH` or `github.action_path` which contains the path in which the composite action is downloaded to and where those "files" live. Note, you'll have to use `chmod` before running each script if you do not git check in your script files into your github repo with the executable bit turned on.

### Inputs

Example `workflow.yml`:

```yaml
steps: 
  - id: foo
    uses: user/composite@v1
    with:
      your_name: "Octocat"
```

Example `user/composite/action.yml`:

```yaml
inputs:
  your_name:
    description: 'Your name'
    default: 'Ethan'
runs:
  using: "composite"
  steps: 
    - run: echo hello ${{ inputs.your_name }}
      shell: bash
```

Example Output:

```
hello Octocat
```

Each input variable in the composite action is only viewable in its own scope.

### Outputs

Example `workflow.yml`:

```yaml
...
steps: 
  - id: foo
    uses: user/composite@v1
  - run: echo random-number ${{ steps.foo.outputs.random-number }} 
    shell: bash
```

Example `user/composite/action.yml`:

```yaml
outputs:
  random-number: 
    description: "Random number"
    value: ${{ steps.random-number-generator.outputs.random-id }}
runs:
  using: "composite"
  steps: 
    - id: random-number-generator
      run: echo "::set-output name=random-id::$(echo $RANDOM)"
      shell: bash
```

Example Output:

```
::set-output name=my-output::43243
random-number 43243
```

Each of the output variables from the composite action is viewable from the workflow file that uses the composite action. In other words, every child's action output(s) are only viewable by its parent using dot notation (ex `steps.foo.outputs.random-number`).

Moreover, the output ids are only accessible within the scope where it was defined. Note that in the example above, in our `workflow.yml` file, it should not have access to output id (i.e. `random-id`). The reason why we are doing this is that we don't want to require the workflow author to know the internal workings of the composite action.

### Context

Similar to the workflow file, the composite action has access to the [same context objects](https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts) (ex: `github`, `env`, `strategy`). 

### Environment

In the Composite Action, you'll only be able to use `::set-env::` to set environment variables just like you could with other actions.

### Secrets

**We will not support "Secrets" in a composite action for now. This functionality will be focused on in a future ADR.**

We'll pass the secrets from the composite action's parents (ex: the workflow file) to the composite action. Secrets can be created in the composite action with the secrets context. In the actions yaml, we'll automatically mask the secret. 


### If-Condition

** `If` and `needs` conditions will not be supported in the composite run steps feature. It will be supported later on in a new feature. **

Old reasoning:

Example `workflow.yml`:

```yaml
steps:
  - run: exit 1
  - uses: user/composite@v1  # <--- this will run, as it's marked as always running
    if: always()
```

Example `user/composite/action.yml`:

```yaml
runs:
  using: "composite"
  steps:
    - run: echo "just succeeding"
      shell: bash
    - run: echo "I will run, as my current scope is succeeding"
      shell: bash
      if: success()
    - run: exit 1
      shell: bash
    - run: echo "I will not run, as my current scope is now failing"
      shell: bash
```

**We will not support "if-condition" in a composite action for now. This functionality will be focused on in a future ADR.**

See the paragraph below for a rudimentary approach (thank you to @cybojenix for the idea, example, and explanation for this approach):

The `if` statement in the parent (in the example above, this is the `workflow.yml`) shows whether or not we should run the composite action. So, our composite action will run since the `if` condition for running the composite action is `always()`.

**Note that the "if-condition" on the parent does not propagate to the rest of its children though.**

In the child action (in this example, this is the `action.yml`), it starts with a clean slate (in other words, no imposing if-conditions). Similar to the logic in the paragraph above, `echo "I will run, as my current scope is succeeding"` will run since the `if` condition checks if the previous steps **within this composite action** have not failed. `run: echo "I will not run, as my current scope is now failing"` will not run since the previous step resulted in an error and by default, the if expression is set to `success()` if the if-condition is not set for a step.


What if a step has `cancelled()`? We do the opposite of our approach above if `cancelled()` is used for any of our composite run steps. We will cancel any step that has this condition if the workflow is cancelled at all.
    
### Timeout-minutes

Example `workflow.yml`:

```yaml
steps: 
  - id: bar
    uses: user/test@v1
    timeout-minutes: 50
```

Example `user/composite/action.yml`:

```yaml
runs:
  using: "composite"
  steps: 
    - id: foo1
      run: echo test 1
      timeout-minutes: 10
      shell: bash
    - id: foo2
      run: echo test 2
      shell: bash
    - id: foo3
      run: echo test 3
      timeout-minutes: 10
      shell: bash
```

**We will not support "timeout-minutes" in a composite action for now. This functionality will be focused on in a future ADR.**

A composite action in its entirety is a job. You can set both timeout-minutes for the whole composite action or its steps as long as the sum of the `timeout-minutes` for each composite action step that has the attribute `timeout-minutes` is less than or equals to `timeout-minutes` for the composite action. There is no default timeout-minutes for each composite action step. 

If the time taken for any of the steps in combination or individually exceeds the whole composite action `timeout-minutes` attribute, the whole job will fail (1). If an individual step exceeds its own `timeout-minutes` attribute but the total time that has been used including this step is below the overall composite action `timeout-minutes`, the individual step will fail but the rest of the steps will run based on their own `timeout-minutes` attribute (they will still abide by condition (1) though).

For reference, in the example above, if the composite step `foo1` takes 11 minutes to run, that step will fail but the rest of the steps, `foo1` and `foo2`, will proceed as long as their total runtime with the previous failed `foo1` action is less than the composite action's `timeout-minutes` (50 minutes). If the composite step `foo2` takes 51 minutes to run, it will cause the whole composite action job to fail.

The rationale behind this is that users can configure their steps with the `if` condition to conditionally set how steps rely on each other. Due to the additional capabilities that are offered with combining `timeout-minutes` and/or `if`, we wanted the `timeout-minutes` condition to be as dumb as possible and not affect other steps. 

[Usage limits still apply](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions?query=if%28%29#usage-limits)


### Continue-on-error

Example `workflow.yml`:

```yaml
steps: 
  - run: exit 1
  - id: bar
    uses: user/test@v1
    continue-on-error: false
  - id: foo
    run: echo "Hello World" <------- This step will not run
```

Example `user/composite/action.yml`:

```yaml
runs:
  using: "composite"
  steps: 
    - run: exit 1
      continue-on-error: true
      shell: bash
    - run: echo "Hello World 2" <----- This step will run
      shell: bash
```

**We will not support "continue-on-error" in a composite action for now. This functionality will be focused on in a future ADR.**

If any of the steps fail in the composite action and the `continue-on-error` is set to `false` for the whole composite action step in the workflow file, then the steps below it will run. On the flip side, if `continue-on-error` is set to `true` for the whole composite action step in the workflow file, the next job step will run.

For the composite action steps, it follows the same logic as above. In this example, `"Hello World 2"` will be outputted because the previous step has `continue-on-error` set to `true` although that previous step errored. 

### Visualizing Composite Action in the GitHub Actions UI
We want all the composite action's steps to be condensed into the original composite action node. 

Here is a visual representation of the [first example](#Steps)

```yaml
| composite_action_node |
    | echo hello world 1 | 
    | echo hello world 2 |
| echo hello world 3 | 
| echo hello world 4 |

```


## Consequences

This ADR lays the framework for eventually supporting nested Composite Actions within Composite Actions. This ADR allows for users to run multiple run steps within a GitHub Composite Action with the support of inputs, outputs, environment, and context for use in any steps as well as the if, timeout-minutes, and the continue-on-error attributes for each Composite Action step.

---

### Adrs 1144 Composite Actions (`docs/adrs/1144-composite-actions.md`)

**Date**: 2021-06-10

**Status**: Accepted

## Context

We released [composite run steps](https://github.com/actions/runner/pull/554) last year which started our journey of reusing steps across different workflow files. To continue that journey, we want to expand composite run steps into composite actions.

We want to support the `uses` steps from workflows in composite actions, including:
  - Container actions
  - Javascript actions
  - Other Composite actions (up to a limit of course!)
  - The pre and post steps these actions can generate

## Guiding Principles

- Composite Actions should function as a single step or action, no matter how many steps it is composed of or how many levels of recursion it has
  - In the future we may add a configurable option to make this no longer the case
- A workflow author should not need to understand the inner workings of a composite action in order to use it
- Composite actions should leverage inputs to get values they need, they will not have full access to the `context` objects. The secrets context will **not** be available to composite actions, users will need to pass these values in as an input.
- Other Actions should **just work** inside a composite action, without any code changes

## Decisions

### Composite Recursion Limit

- We will start with supporting a recursion limit of `10` composite actions deep
- We are free to bump this limit in the future, the code will be written to just require updating a variable. If the graph evaluates beyond the recursion limit, the job will fail in the pre-job phase (The `Set up job` step).
- A composite actions interface is its inputs and outputs, nothing else is carried over when invoking recursively.

### Pre/Post Steps in nested Actions

- We do not plan on adding the ability to configure a customizable pre or post step for composite actions at this time. However, we will execute the pre and post steps of any actions referenced in a composite action.
- Composite actions will generate a single pre-step and post-step for the entire composite action, even if there are multiple pre-steps and post-steps in the referenced actions.
  - These steps will execute following the same ordering rules we have today, first to run has their pre step run first and their post step run last.
  - For example, if you had a composite action with two pre steps and two posts steps:

  ```
  - uses: action1
  - uses: composite1
  - uses: action2
  ```

  The order of execution would be:

  ```
  - prestep-action1
  - prestep-composite1
    - prestep-composite1-first-action-referenced
    - prestep-composite1-second-action-referenced
  - prestep-action2
  - the job steps
  - poststep-action2
  - poststep-composite1
    - poststep-composite1-the-second-action-referenced
    - poststep-composite1-first-action-referenced
  - poststep-action1
  ```

#### Set-state

- While the composite action has an individual combined pre/post action, the `set-state` command will not be shared.
- If the `set-state` command is used during a composite step, only the action that originally called `set-state` will have access to the env variable during the post run step.
  - This prevents multiple actions that set the same state from interfering with the execution of another action's post step.

### Resolve Action Endpoint changes

- The resolve actions endpoint will now validate policy to ensure that the given workflow run has access to download that action.
  - Older GHES/GHAE customers with newer runners will be locked out of composite uses steps until they upgrade their instance.

### Local actions
- Local actions will expand the tree, perform policy checks, and download actions Just in Time when the step is running.
- Like current local actions, we will not support presteps. If an action is running local, by the time we know that, the time to run presteps have already passed.

### If, continue-on-error, timeout-minutes - Not being considered at this time

- `if`, `continue-on-error`, `timeout-minutes` could be supported in composite run/uses steps. These values were not originally supported in our composite run steps implementation.
  - Browsing the community forums and runner repo, there hasn't been a lot of noise asking for these features, so we will hold off on them.
- These values passed as input into the composite action will **not** be carried over as input into the individual steps the composite action runs.

### Defaults - Not being considered at this time

- In actions, we have the idea of [defaults](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#defaultsrun) , which allow you to specify a shell and working directory in one location, rather then on each step.
- However, `shell` is currently required in composite run steps
  - In regular run steps, it is optional, and defaults to a different value based on the OS.
- We want to prioritize the right experience for the consumer, and make the action author continue to explicitly set these values. We can consider improving this experience in the future.

## Consequences

- Workflows are now more reusable across multiple workflow files
- Composite actions implement most of the existing workflow run steps, with room to expand these in the future
- Feature flags will control this rollout

---

### Adrs 1438 Conditional Composite (`docs/adrs/1438-conditional-composite.md`)

# ADR 1438: Support Conditionals In Composite Actions

**Date**: 2021-10-13

**Status**: Accepted

## Context

We recently shipped composite actions, which allows you to reuse individual steps inside an action. 
However, one of the [most requested features](https://github.com/actions/runner/issues/834) has been a way to support the `if` keyword.

### Goals
- We want to keep consistent with current behavior
- We want to support conditionals via the `if` keyword
- Our built in functions like `success` should be implementable without calling them, for example you can do `job.status == success` rather then `success()` currently.

### How does composite currently work?

Currently, we have limited conditional support in composite actions for `pre` and `post` steps. 
These are based on the `job status`, and support keywords like `always()`, `failed()`, `success()` and `cancelled()`. 
However, generic or main steps do **not** support conditionals.

By default, in a regular workflow, a step runs on the `success()` condition. Which looks at the **job** **status**, sees if it is successful and runs.

By default, in a composite action, main steps run until a single step fails in that composite action, then the composite action is halted early. It does **not** care about the job status.
Pre, and post steps in composite actions use the job status to determine if they should run.

### How do we go forward?

Well, if we think about what composite actions are currently doing when invoking main steps, they are checking if the current composite action is successful. 
Lets formalize that concept into a "real" idea.

- We will add an `action_status` field to the github context to mimic the [job's context status](https://docs.github.com/en/actions/learn-github-actions/contexts#job-context).
  - We have an existing concept that does this `action_path` which is only set for composite actions on the github context.
- In a composite action during a main step, the `success()` function will check if `action_status == success`, rather then `job_status == success`. Failure will work the same way. 
  - Pre and post steps in composite actions will not change, they will continue to check the job status.


### Nested Scenario
For nested composite actions, we will follow the existing behavior, you only care about your current composite action, not any parents. 
For example, lets imagine a scenario with a simple nested composite action

```
- Job
  - Regular Step
  - Composite Action
    - runs: exit 1
    - if: always()
      uses: A child composite action
        - if: success()
          runs: echo "this should print"
        - runs: echo "this should also print"
    - if: success()
      runs: echo "this will not print as the current composite action has failed already"
      
```
The child composite actions steps should run in this example, the child composite action has not yet failed, so it should run all steps until a step fails. This is consistent with how a composite action currently works in production if the main job fails but a composite action is invoked with `if:always()` or `if: failure()`

### Other options explored
We could add the `current_step_status` to the job context rather then `__status` to the steps context, however this comes with two major downsides:
- We need to support the field for every type of step, because its non trivial to remove a field from the job context once it has been added (its readonly)
  - For all actions besides composite it would only every be `success`
  - Its weird to have a `current_step` value on the job context
- We also explored a `__status` on the steps context.
  - The `__` is required to prevent us from colliding with a step with id: status
  - This felt wrong because the naming was not smooth, and did not fit into current conventions.

### Consequences
- github context has a new field for the status of the current composite action.
- We support conditional's in composite actions
- We keep the existing behavior for all users, but allow them to expand that functionality.

---

### Adrs 1751 Runner Job Hooks (`docs/adrs/1751-runner-job-hooks.md`)

# ADR: Notification Hooks for Runners

## Context

This ADR details the design changes for supporting custom configurable hooks for on various runner events. This has been a long requested user feature [here](https://github.com/actions/runner/issues/1543), [here](https://github.com/actions/runner/issues/699) and [here](https://github.com/actions/runner/issues/1116) for users to have more information on runner observability, and for the ability to run cleanup and teardown jobs. 

This feature is mainly intended for self hosted runner administrators.

**What we hope to solve with this feature**
1. A runner admininstrator is able to add custom scripts to cleanup their runner environment at the start or end of a job
2. A runner admininstrator is able to add custom scripts to help setup their runner environment at the beginning of a job, for reasons like [caching](https://github.com/actions/runner/issues/1543#issuecomment-1050346279)
3. A runner administrator is able to grab custom telemetry of jobs running on their self hosted runner

**What we don't think this will solve**
- Policy features that require certain steps run at the beginning or end of all jobs
  - This would be better solved to in a central place in settings, rather then decentralized on each runner. 
  - The Proposed `Notification Hooks for Runners` is limited to self hosted runners, we don't beileve Policy features should be
- Reuse scenarios between jobs are covered by [composite actions](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action) and [resuable workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows)
- Security applications, security should be handled on the policy side on the server, not decentralized on each runner

## Hooks
- We will expose 2 variables that users can set to enable hooks  
  - `ACTIONS_RUNNER_HOOK_JOB_STARTED`
  - `ACTIONS_RUNNER_HOOK_JOB_COMPLETED`

You can set these variables to the **absolute** path of a `.sh` or `.ps1` file.

We will execute `pwsh` (fallback to `powershell`) or `bash` (fallback to `sh`) as appropriate.
- `.sh` files will execute with the args `-e {pathtofile}`
- `.ps1` files will execute with the args `-command \". '{pathtofile}'\"`

We will **not** set the [standard flags we typically set](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell) for `runs` commands. So, if you want to set `pipefail` on `bash` for example, you will need to do that in your script.

### UI
We want to ensure the experience for users invoking workflows is good, if hooks take too long, you may feel your job is delayed or broken. So, much like `Set Up Job`, we will generate two new steps automatically in your job, one for each configured hook: 
- `Set up runner`  
- `Complete runner` 

These steps will contain all of the output from invoking your hook, so you will have visibility into the runtime. We will also provide information on the path to the hook, and what shell we are invoking it as, much like we do for `run: ` steps.

### Contexts
When running your hooks, some context on your job may be helpful.
- The scripts will have access to the standard [default environment variables](https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables)
  - Some of these variables are step specific like `GITHUB_ACTION`, in which case they will not be set
- You can pull the full webhook event payload from `GITHUB_EVENT_PATH`

### Commands
Should we expose [Commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) and [Environment Files](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files)

**Yes**. Imagine a scenario where a runner administrator is deprecating a runner pool, and they need to [warn users](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-warning-message) to swap to a different pool, we should support them in doing this. However, there are some limitations:
- [save-state](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions) will **not** be supported, these are not traditional steps with pre and post actions
- [set-output](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#using-workflow-commands-to-access-toolkit-functions) will **not** be supported, there is no `id` as this is not a traditional step


### Environment Files
We will also enable [Environment Files](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files) to support setup scenarios for the runner environment.

While a self hosted runner admin can [set env variables](https://docs.github.com/en/actions/hosting-your-own-runners/using-a-proxy-server-with-self-hosted-runners#using-a-env-file-to-set-the-proxy-configuration), these apply to all jobs. By enabling the ability to `add a path` and `set an env` we give runner admins the ability to do this dynamically based on the [workflows environment variables](https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables) to empower setup scenarios.


### Exit codes
These are **synchronous** hooks, so they will block job execution while they are being run. Exit code 0 will indicate a successful run of the hook and we will proceed with the job, any other exit code will fail the job with an appropriate annotation.
- There will be no support for `continue-on-error`

## Key Decisions
- We will expose 2 variables that users can set to enable hooks  
  - `ACTIONS_RUNNER_HOOK_JOB_STARTED`
  - `ACTIONS_RUNNER_HOOK_JOB_COMPLETED`
- Users can set these variables to the path of a `.sh` or `.ps1` file, which we will execute when Jobs are started or completed.
  - Output from these will be added to a new step at the start/end of a job named `Set up runner` or `Complete runner`. 
    - These steps will only be generated on runs with these hooks
- These hooks `always()` execute if the env variable is set
- These files will execute as the Runner user, outside of any container specification on the job
- These are **synchronous** hooks
  - Runner admins can execute a background process for async hooks if they want
  - We will fail the job and halt execution on any exit code that is not 0. The Runner admin is responsible for returning the correct exit code and ensuring resilency. 
    - This includes that the runner user needs access to the file in the env and the file must exist
    - There will be no `continue-on-error` type option on launch
    - There will be no `timeout` option on launch

## Consequences
- Runner admins have the ability to tie into the runner job execution to publish their own telemetry or perform their own cleanup or setup
- New steps will be added to the UI showcasing the output of these hooks

---

### Adrs 1891 Container Hooks (`docs/adrs/1891-container-hooks.md`)

# ADR 0000: Container Hooks

**Date**: 2022-05-12

**Status**: Accepted

# Background

[Job Hooks](https://github.com/actions/runner/blob/main/docs/adrs/1751-runner-job-hooks.md) have given users the ability to customize how their self hosted runners run a job.
Users also want the ability to customize how they run containers during the scope of the job, rather then being locked into the docker implementation we have in the runner. They may want to use podman, kubernetes, or even change the docker commands we run. 
We should give them that option, and publish examples how how they can create their own hooks.

# Guiding Principles
- **Extensibility** is the focus, we need to make sure we are flexible enough to cover current and future scenarios, even at the cost of making it harder to utilize these hooks
- Args should map **directly** to yaml values provided by the user. 
  - For example, the current runner overrides `HOME`, we can do that in the hook, but we shouldn't pass that hook as an ENV with the other env's the user has set, as that is not user input, it is how the runner invokes containers

## Interface
- You will set the variable `ACTIONS_RUNNER_CONTAINER_HOOKS=/Users/foo/runner/hooks.js` which is the entrypoint to your hook handler.
  - There is no partial opt in, you must handle every hook 
- We will pass a command and some args via `stdin`
- An exit code of 0 is a success, every other exit code is a failure
- We will support the same runner commands we support in [Job Hooks](https://github.com/actions/runner/blob/main/docs/adrs/1751-runner-job-hooks.md)
- On timeout, we will send a sigint to your process. If you fail to terminate within a reasonable amount of time, we will send a sigkill, and eventually kill the process tree.

An example input looks like
```json
{
  "command": "job_cleanup",
  "responseFile": "/users/thboop/runner/_work/{guid}.json",
  "args": {},
  "state": 
  {
      "id": "82e8219701fe096a35941d869cf8d71af1d943b5d3bdd718850fb87ac3042480"
  }
}
```

`command` is the command we expect you to invoke
`responseFile` is the file you need to write your output to, if the command has output
`args` are the specific arguments the command needs
`state` is a json blog you can pass around to maintain your state, this is covered in more details below.

### Writing responses to a file
All text written to stdout or stderr should appear in the job or step logs. With that in mind, we support a few ways to actually return data:
1. Wrapping the json in some unique tag and processing it like we do commands
2. Writing to a file

For 1, users typically view logging information as a safe action, so we worry someone accidentialy logging unsantized information and causing unexpected or un-secure behavior. We eventually plan to move off of stdout/stderr style commands in favor of a runner cli. 
Investing in this area doesn't make a lot of sense at this time.

While writing to a file to communicate isn't the most ideal pattern, its an existing pattern in the runner and serves us well, so lets reuse it.

### Output
Your output must be correctly formatted json. An example output looks like:

```
{
  "state": {},
  "context"
  {
    "container" : 
    {
      "id": "82e8219701fe096a35941d869cf8d71af1d943b5d3bdd718850fb87ac3042480"
      "network": "github_network_53269bd575974817b43f4733536b200c"
    }
    "services": {
      "redis": {
        "id": "60972d9aa486605e66b0dad4abb638dc3d9116f566579e418166eedb8abb9105",
        "ports": {
          "8080": "8080"
        },
      "network": "github_network_53269bd575974817b43f4733536b200c"
    }
  }
  "alpine: true,
}
```

`state` is a unique field any command can return. If it is not empty, we will store the state for you and pass it into all future commands. You can overwrite it by having the next hook invoked return a unique state.

Other fields are dependent upon the command being run.

### Versioning
We will not version these hooks at launch. If needed, we can always major version split these hooks in the future. We will ship in Beta to allow for breaking changes for a few months.

### The Job Context
The [job context](https://docs.github.com/en/actions/learn-github-actions/contexts#example-contents-of-the-job-context) currently has a variety of fields that correspond to containers. We should consider allowing hooks to populate new fields in the job context. That is out of scope for this original release however.

## Hooks
Hooks are to be implemented at a very high level, and map to actions the runner does, rather then specific docker actions like `docker build` or `docker create`. By mapping to runner actions, we create a very extensible framework that is flexible enough to solve any user concerns in the future. By providing first party implementations, we give users easy starting points to customize specific hooks (like `docker build`) without having to write full blown solutions.

The other would be to provide hooks that mirror every docker call we make, and expose more hooks to help support k8s users, with the expectation that users may have to no-op on multiple hooks if they don't correspond to our use case.

Why we don't want to go that way
- It feels clunky, users need to understand which hooks they need to implement and which they can ignore, which isn't a great UX
- It doesn't scale well, I don't want to build a solution where we may need to add more hooks, by mapping to runner actions, updating hooks is a painful experience for users
- Its overwhelming, its easier to tell users to build 4 hooks and track data themselves, rather then 16 hooks where the runner needs certain information and then needs to provide that information back into each hook. If we expose `Container Create`, you need to return the container you created, then we do `container run` which uses that container. If we just give you an image and say create and run this container, you don't need to store the container id in the runner, and it maps better to k8s scenarios where we don't really have container ids.

### Prepare_job hook
The `prepare_job` hook is called when a job is started. We pass in any job or service containers the job has. We expect that you:
- Prune anything from previous jobs if needed
- Create a network if needed
- Pull the job and service containers
- Start the job container
- Start the service containers
- Write to the response file some information we need
  - Required: if the container is alpine, otherwise x64
  - Optional: any context fields you want to set on the job context, otherwise they will be unavailable for users to use
- Return 0 when the health checks have succeeded and the job/service containers are started

This hook will **always** be called if you have container hooks enabled, even if no service or job containers exist in the job. This allows you to fail the job or implement a default job container if you want to and no job container has been provided.


<details>
<summary>Example Input</summary>
<br>
  
```
/* Detailed source-code truncated for AI context efficiency. */
```

</details>

<details>
<summary>Field Descriptions</summary>
<br>

```
/* Detailed source-code truncated for AI context efficiency. */
```

</details>

<details>
<summary>Example Output</summary>
<br>

```
{
  "state": 
  {
    "network": "github_network_53269bd575974817b43f4733536b200c",
    "jobContainer" : "82e8219701fe096a35941d869cf8d71af1d943b5d3bdd718850fb87ac3042480",
    "serviceContainers": 
    {
      "redis": "60972d9aa486605e66b0dad4abb638dc3d9116f566579e418166eedb8abb9105"
    }
  },
  "context"
  {
    "container" : 
    {
      "id": "82e8219701fe096a35941d869cf8d71af1d943b5d3bdd718850fb87ac3042480"
      "network": "github_network_53269bd575974817b43f4733536b200c"
    }
    "services": {
      "redis": {
        "id": "60972d9aa486605e66b0dad4abb638dc3d9116f566579e418166eedb8abb9105",
        "ports": {
          "8080": "8080"
        },
      "network": "github_network_53269bd575974817b43f4733536b200c"
    }
  }
  "alpine: true,
}
```

</details>


### Cleanup Job
The `cleanup_job` hook is called at the end of a job and expects you to:
- Stop any running service or job containers (or the equiavalent pod)
- Stop the network (if one exists)
- Delete any job or service containers (or the equiavalent pod)
- Delete the network (if one exists)
- Cleanup anything else that was created for the run

Its input looks like

<details>
<summary>Example Input</summary>
<br>

```
  "command": "cleanup_job",
  "responseFile": null,
  "state":
  {
    "network": "github_network_53269bd575974817b43f4733536b200c",
    "jobContainer" : "82e8219701fe096a35941d869cf8d71af1d943b5d3bdd718850fb87ac3042480",
    "serviceContainers": 
    {
      "redis": "60972d9aa486605e66b0dad4abb638dc3d9116f566579e418166eedb8abb9105"
    }
  }
  "args": {}
```
  
</details>

No args are provided.
  
No output is expected.


### Run Container Step
The `run_container_step` is called once per container action in your job and expects you to:
- Pull or build the required container (or fail if you cannot)
- Run the container action and return the exit code of the container
- Stream any step logs output to stdout and stderr
- Cleanup the container after it executes

<details>
<summary>Example Input for Image</summary>
<br>

```
/* Detailed source-code truncated for AI context efficiency. */
```
  
</details>


<details>
<summary>Example Input for dockerfile</summary>
<br>

```
/* Detailed source-code truncated for AI context efficiency. */
```
  
</details>


<details>
<summary>Field Descriptions</summary>
<br>

```
/* Detailed source-code truncated for AI context efficiency. */
```
  
</details>

No output is expected

Currently we build all container actions at the start of the job. By doing it during the hook, we move this to just in time building for hooks. We could expose a hook to build/pull a container action, and have those called at the start of a job, but doing so would require hook authors to track the build containers in the state, which could be painful.

### Run Script Step
The `run_script_step` expects you to:
- Invoke the provided script inside the job container and return the exit code
- Stream any step log output to stdout and stderr

<details>
<summary>Example Input</summary>
<br>


```
  "command": "run_script_step",
  "responseFile": null,
  "state":
  {
    "network": "github_network_53269bd575974817b43f4733536b200c",
    "jobContainer" : "82e8219701fe096a35941d869cf8d71af1d943b5d3bdd718850fb87ac3042480",
    "serviceContainers": 
    {
      "redis": "60972d9aa486605e66b0dad4abb638dc3d9116f566579e418166eedb8abb9105"
    }
  }
  "args": 
  {
    "entryPointArgs": ["-e", "/runner/temp/abc123.sh"],
    "entryPoint": "bash",
    "environmentVariables": {
      "NODE_ENV": "development"
    },
    "prependPath": ["/foo/bar", "bar/foo"],
    "workingDirectory": "/__w/thboop-test2/thboop-test2"
  }
```
  
</details>

<details>
<summary>Field Descriptions</summary>
<br>

```
Arg Fields:

  
"entryPointArgs": **Optional** A list containing the entry point args
"entryPoint": **Optional** The container entry point to use if the default image entrypoint should be overwritten
"prependPath": **Optional** an array of additional paths to prepend to the $PATH variable
"workingDirectory": **Required** A string containing the absolute path of the working directory
"environmentVariables": **Optional** A map of key value env's to set
```

</details>

No output is expected


## Limitations
- We will only support linux on launch
- Hooks are set by the runner admin, and thus are only supported on self hosted runners 

## Consequences
- We support non docker scenarios for self hosted runners and allow customers to customize their docker invocations
- We ship/maintain docs on docker hooks and an open source repo with examples
- We support these hooks and add enough telemetry to be able to troubleshoot support issues as they come in.

---

### Adrs 2494 Runner Image Tags (`docs/adrs/2494-runner-image-tags.md`)

# ADR 2494: Runner Image Tags

**Date**: 2023-03-17

**Status**: Accepted

## Context

Following the [adoption of actions-runner-controller by GitHub](https://github.com/actions/actions-runner-controller/discussions/2072) and the introduction of the new runner scale set autoscaling mode, we needed to provide a basic runner image that could be used off the shelf without much friction.

The [current runner image](https://github.com/actions/runner/pkgs/container/actions-runner) is published to GHCR. Each release of this image is tagged with the runner version and the most recent release is also tagged with `latest`.

While the use of `latest` is common practice, we recommend that users pin a specific version of the runner image for a predictable runtime and improved security posture. However, we still notice that a large number of end users are relying on the `latest` tag & raising issues when they encounter problems.

Add to that, the community actions-runner-controller maintainers have issued a [deprecation notice](https://github.com/actions/actions-runner-controller/issues/2056) of the `latest` tag for the existing runner images (https://github.com/orgs/actions-runner-controller/packages).

## Decision

Proceed with Option 2, keeping the `latest` tag and adding the `NOTES.txt` file to our helm charts with the notice.

### Option 1: Remove the `latest` tag

By removing the `latest` tag, we have to proceed with either of these options:

1. Remove the runner image reference in the `values.yaml` provided with the `gha-runner-scale-set` helm chart and mark these fields as required so that users have to explicitly specify a runner image and a specific tag. This will obviously introduce more  friction for users who want to start using actions-runner-controller for the first time.

```yaml
  spec:
    containers:
    - name: runner
      image: ""
      tag: ""
      command: ["/home/runner/run.sh"]
```

1. Pin a specific runner image tag in the `values.yaml` provided with the `gha-runner-scale-set` helm chart. This will reduce friction for users who want to start using actions-runner-controller for the first time but will require us to update the `values.yaml` with every new runner release.

```yaml
  spec:
    containers:
    - name: runner
      image: "ghcr.io/actions/actions-runner"
      tag: "v2.300.0"
      command: ["/home/runner/run.sh"]
```

### Option 2: Keep the `latest` tag

Keeping the `latest` tag is also a reasonable option especially if we don't expect to make any breaking changes to the runner image. We could enhance this by adding a [NOTES.txt](https://helm.sh/docs/chart_template_guide/notes_files/) to the helm chart which will be displayed to the user after a successful helm install/upgrade. This will help users understand the implications of using the `latest` tag and how to pin a specific version of the runner image.

The runner image release workflow will need to be updated so that the image is pushed to GHCR and tagged only when the runner rollout has reached all scale units.

## Consequences

Proceeding with **option 1** means:

1. We will enhance the runtime predictability and security posture of our end users
1. We will have to update the `values.yaml` with every new runner release (that can be automated)
1. We will introduce friction for users who want to start using actions-runner-controller for the first time

Proceeding with **option 2** means:

1. We will have to continue to maintain the `latest` tag
1. We will assume that end users will be able to handle the implications of using the `latest` tag
1. Runner image release workflow needs to be updated

---

### Checks Actions (`docs/checks/actions.md`)

# Actions Connection Check

## What is this check for?

Make sure the runner has access to actions service for GitHub.com or GitHub Enterprise Server

- For GitHub.com
  - The runner needs to access `https://api.github.com` for downloading actions.
  - The runner needs to access `https://codeload.github.com` for downloading actions tar.gz/zip.
  - The runner needs to access `https://vstoken.actions.githubusercontent.com/_apis/.../` for requesting an access token.
  - The runner needs to access `https://pipelines.actions.githubusercontent.com/_apis/.../` for receiving workflow jobs.
  - The runner needs to access `https://results-receiver.actions.githubusercontent.com/.../` for reporting progress and uploading logs during a workflow job execution.
  ---
  **NOTE:** for the full list of domains that are required to be in the firewall allow list refer to the [GitHub self-hosted runners requirements documentation](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github).

  These can by tested by running the following `curl` commands from your self-hosted runner machine:

    ```
    curl -v https://api.github.com/zen
    curl -v https://codeload.github.com/_ping
    curl -v https://vstoken.actions.githubusercontent.com/_apis/health
    curl -v https://pipelines.actions.githubusercontent.com/_apis/health
    curl -v https://results-receiver.actions.githubusercontent.com/health
    ```

- For GitHub Enterprise Server
  - The runner needs to access `https://[hostname]/api/v3` for downloading actions.
  - The runner needs to access `https://codeload.[hostname]/_ping` for downloading actions tar.gz/zip.
  - The runner needs to access `https://[hostname]/_services/vstoken/_apis/.../` for requesting an access token.
  - The runner needs to access `https://[hostname]/_services/pipelines/_apis/.../` for receiving workflow jobs.
  
  These can by tested by running the following `curl` commands from your self-hosted runner machine, replacing `[hostname]` with the hostname of your appliance, for instance `github.example.com`:

    ```
    curl -v https://[hostname]/api/v3/zen
    curl -v https://codeload.[hostname]/_ping
    curl -v https://[hostname]/_services/vstoken/_apis/health
    curl -v https://[hostname]/_services/pipelines/_apis/health
    ```

    A common cause of this these connectivity issues is if your to GitHub Enterprise Server appliance is using [the self-signed certificate that is enabled the first time](https://docs.github.com/en/enterprise-server/admin/configuration/configuring-network-settings/configuring-tls) your appliance is started. As self-signed certificates are not trusted by web browsers and Git clients, these clients (including the GitHub Actions runner) will report certificate warnings.
    
    We recommend [upload a certificate signed by a trusted authority](https://docs.github.com/en/enterprise-server/admin/configuration/configuring-network-settings/configuring-tls) to GitHub Enterprise Server, or enabling the built-in ][Let's Encrypt support](https://docs.github.com/en/enterprise-server/admin/configuration/configuring-network-settings/configuring-tls).


## What is checked?

- DNS lookup for api.github.com or myGHES.com using dotnet
- Ping api.github.com or myGHES.com using dotnet
- Make HTTP GET to https://api.github.com or https://myGHES.com/api/v3 using dotnet, check response headers contains `X-GitHub-Request-Id`
---
- DNS lookup for codeload.github.com or codeload.myGHES.com using dotnet
- Ping codeload.github.com or codeload.myGHES.com using dotnet
- Make HTTP GET to https://codeload.github.com/_ping or https://codeload.myGHES.com/_ping using dotnet, check response headers contains `X-GitHub-Request-Id`
---
- DNS lookup for vstoken.actions.githubusercontent.com using dotnet
- Ping vstoken.actions.githubusercontent.com using dotnet
- Make HTTP GET to https://vstoken.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/vstoken/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
---
- DNS lookup for pipelines.actions.githubusercontent.com using dotnet
- Ping pipelines.actions.githubusercontent.com using dotnet
- Make HTTP GET to https://pipelines.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/pipelines/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
- Make HTTP POST to https://pipelines.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/pipelines/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
---
- DNS lookup for results-receiver.actions.githubusercontent.com using dotnet
- Ping results-receiver.actions.githubusercontent.com using dotnet
- Make HTTP GET to https://results-receiver.actions.githubusercontent.com/health using dotnet, check response headers contains `X-GitHub-Request-Id`

## How to fix the issue?

### 1. Check the common network issue
  
  > Please check the [network doc](./network.md)

### 2. SSL certificate related issue

  If you are seeing `System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.` in the log, it means the runner can't connect to Actions service due to SSL handshake failure.
  > Please check the [SSL cert doc](./sslcert.md)
  
## Still not working?

Contact [GitHub Support](https://support.github.com) if you have further questuons, or log an issue at https://github.com/actions/runner if you think it's a runner issue.

---

### Checks Git (`docs/checks/git.md`)

# Git Connection Check

## What is this check for?

Make sure `git` can access GitHub.com or your GitHub Enterprise Server.


## What is checked?

The test is done by executing
```bash
# For GitHub.com
git ls-remote --exit-code https://github.com/actions/checkout HEAD

# For GitHub Enterprise Server
git ls-remote --exit-code https://ghes.me/actions/checkout HEAD
```

The test also set environment variable `GIT_TRACE=1` and `GIT_CURL_VERBOSE=1` before running `git ls-remote`, this will make `git` to produce debug log for better debug any potential issues.

## How to fix the issue?

### 1. Check global and system git config

If you are having issues connecting to the server, check your global and system git config for any unexpected authentication headers. You might be seeing an error like:

```
fatal: unable to access 'https://github.com/actions/checkout/': The requested URL returned error: 400
```

The following commands can be used to check for unexpected authentication headers:

```
$ git config --global --list | grep extraheader
http.extraheader=AUTHORIZATION: unexpected_auth_header

$ git config --system --list | grep extraheader
```

The following command can be used to remove the above value: `git config --global --unset http.extraheader`

### 2. Check the common network issue
  
  > Please check the [network doc](./network.md)

### 3. SSL certificate related issue

  If you are seeing `SSL Certificate problem:` in the log, it means the `git` can't connect to the GitHub server due to SSL handshake failure.
  > Please check the [SSL cert doc](./sslcert.md)
  
## Still not working?

Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.

---

### Checks Internet (`docs/checks/internet.md`)

# Internet Connection Check

## What is this check for?

Make sure the runner has access to https://api.github.com

The runner needs to access https://api.github.com to download any actions from the marketplace.

Even the runner is configured to GitHub Enterprise Server, the runner can still download actions from GitHub.com with [GitHub Connect](https://docs.github.com/en/enterprise-server@2.22/admin/github-actions/enabling-automatic-access-to-githubcom-actions-using-github-connect)


## What is checked?

- DNS lookup for api.github.com using dotnet
- Ping api.github.com using dotnet
- Make HTTP GET to https://api.github.com using dotnet, check response headers contains `X-GitHub-Request-Id`

## How to fix the issue?

### 1. Check the common network issue
  
  > Please check the [network doc](./network.md)

## Still not working?

Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.

---

### Checks Network (`docs/checks/network.md`)

## Common Network Related Issues

### Common things that can cause the runner to not working properly

- A bug in the runner or the dotnet framework that causes the actions runner to be unable to make Http requests in a certain network environment.

- A Proxy or Firewall may block certain HTTP method, such as blocking all POST and PUT calls which the runner will use to upload logs.

- A Proxy or Firewall may only allows requests with certain user-agent to pass through and the actions runner user-agent is not in the allow list.

- A Proxy try to decrypt and exam HTTPS traffic for security purpose but cause the actions-runner to fail to finish SSL handshake due to the lack of trusting proxy's CA.

- The SSL handshake may fail if the client and server do not support the same TLS version, or the same cipher suites.

- A Proxy may try to modify the HTTPS request (like add or change some http headers) and causes the request become incompatible with the Actions Service (ASP.NetCore), Ex: [Nginx](https://github.com/dotnet/aspnetcore/issues/17081)

- Firewall rules that block action runner from accessing [certain hosts](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github), ex: `*.github.com`, `*.actions.githubusercontent.com`, etc


### Identify and solve these problems

The key is to figure out where is the problem, the network environment, or the actions runner?

Use a 3rd party tool to make the same requests as the runner did would be a good start point.

- Use `nslookup` to check DNS
- Use `ping` to check Ping
- Use `traceroute`, `tracepath`, or `tracert` to check the network route between the runner and the Actions service
- Use `curl -v` to check the network stack, good for verifying default certificate/proxy settings.
- Use `Invoke-WebRequest` from `pwsh` (`PowerShell Core`) to check the dotnet network stack, good for verifying bugs in the dotnet framework.

If the 3rd party tool is also experiencing the same error as the runner does, then you might want to contact your network administrator for help.

Otherwise, contact GitHub customer support or log an issue at https://github.com/actions/runner

### Troubleshooting: Why can't I configure a runner?

If you are having trouble connecting, try these steps:

1. Validate you can reach our endpoints from your web browser. If not, double check your local network connection
    - For hosted Github:
      - https://api.github.com/
      - https://vstoken.actions.githubusercontent.com/_apis/health
      - https://pipelines.actions.githubusercontent.com/_apis/health
      - https://results-receiver.actions.githubusercontent.com/health
    - For GHES/GHAE
      - https://myGHES.com/_services/vstoken/_apis/health
      - https://myGHES.com/_services/pipelines/_apis/health
      - https://myGHES.com/api/v3
2. Validate you can reach those endpoints in powershell core
    - The runner runs on .net core, lets validate the local settings for that stack
    - Open up `pwsh`
    - Run the command using the urls above `Invoke-WebRequest {url}`
3. If not, get a packet trace using a tool like wireshark and start looking at the TLS handshake.
    - If you see a Client Hello followed by a Server RST:
      - You may need to configure your TLS settings to use the correct version
        - You should support TLS version 1.2 or later
      - You may need to configure your TLS settings to have up to date cipher suites, this may be solved by system updates and patches.
        - Most notably, on windows server 2012 make sure [the tls cipher suite update](https://support.microsoft.com/en-us/topic/update-adds-new-tls-cipher-suites-and-changes-cipher-suite-priorities-in-windows-8-1-and-windows-server-2012-r2-8e395e43-c8ef-27d8-b60c-0fc57d526d94) is installed
      - Your firewall, proxy or network configuration may be blocking the connection
      - You will want to reach out to whoever is in charge of your network with these pcap files to further troubleshoot
    - If you see a failure later in the handshake:
      - Try the fix in the [SSLCert Fix](./sslcert.md)

---

### Checks Nodejs (`docs/checks/nodejs.md`)

# Node.js Connection Check

## What is this check for?

Make sure the built-in node.js has access to GitHub.com or GitHub Enterprise Server.

The runner carries its own copies of node.js executables under `<runner_root>/externals/node20/` and `<runner_root>/externals/node24/`.

All javascript base Actions will get executed by the built-in `node` at either `<runner_root>/externals/node20/` or `<runner_root>/externals/node24/` depending on the version specified in the action's metadata.

The runner's own internal operations (for example `hashFiles()` and this check) use `<runner_root>/externals/node24/`. Set `ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION=node20` to use `node20` instead.

> Not the `node` from `$PATH`

## What is checked?

- Make HTTPS GET to https://api.github.com or https://myGHES.com/api/v3 using node.js, make sure it gets 200 response code.

## How to fix the issue?

### 1. Check the common network issue
  
  > Please check the [network doc](./network.md)

### 2. SSL certificate related issue

  If you are seeing `Https request failed due to SSL cert issue` in the log, it means the `node.js` can't connect to the GitHub server due to SSL handshake failure.
  > Please check the [SSL cert doc](./sslcert.md)
  
## Still not working?

Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.

---

### Checks Sslcert (`docs/checks/sslcert.md`)

## SSL Certificate Related Issues

You might run into an SSL certificate error when your GitHub Enterprise Server is using a self-signed SSL server certificate or a web proxy within your network is decrypting HTTPS traffic for a security audit.

As long as your certificate is generated properly, most of the issues should be fixed after your trust the certificate properly on the runner machine.

> Different OS might have extra requirements on SSL certificate,
> Ex: macOS requires `ExtendedKeyUsage` https://support.apple.com/en-us/HT210176

### Don't skip SSL cert validation

> !!! DO NOT SKIP SSL CERT VALIDATION !!!  
> !!! IT IS A BAD SECURITY PRACTICE !!!  

### Download SSL certificate chain

Depends on how your SSL server certificate gets configured, you might need to download the whole certificate chain from a machine that has trusted the SSL certificate's CA.

- Approach 1: Download certificate chain using a browser (Chrome, Firefox, IT), you can google for more example, [here is what I found](https://medium.com/@menakajain/export-download-ssl-certificate-from-server-site-url-bcfc41ea46a2)

- Approach 2: Download certificate chain using OpenSSL, you can google for more example, [here is what I found](https://superuser.com/a/176721)

- Approach 3: Ask your network administrator or the owner of the CA certificate to send you a copy of it

### Trust CA certificate for the Runner

The actions runner is a dotnet core application which will follow how dotnet load SSL CA certificates on each OS.

You can get full details documentation at [here](https://docs.microsoft.com/en-us/dotnet/standard/security/cross-platform-cryptography#x509store)

In short:
- Windows: Load from Windows certificate store.
- Linux: Load from OpenSSL CA cert bundle.
- macOS: Load from macOS KeyChain.

To let the runner trusts your CA certificate, you will need to:
1. Save your SSL certificate chain which includes the root CA and all intermediate CAs into a `.pem` file.
2. Use `OpenSSL` to convert `.pem` file to a proper format for different OS, here is some [doc with sample commands](https://www.sslshopper.com/ssl-converter.html)
3. Trust CA on different OS:
    - Windows: https://docs.microsoft.com/en-us/skype-sdk/sdn/articles/installing-the-trusted-root-certificate
    - macOS: 
    - Linux: Refer to the distribution documentation
      1. RedHat: https://www.redhat.com/sysadmin/ca-certificates-cli
      2. Ubuntu: http://manpages.ubuntu.com/manpages/focal/man8/update-ca-certificates.8.html
      3. Google search: "trust ca certificate on [linux distribution]"
      4. If all approaches failed, set environment variable `SSL_CERT_FILE` to the CA bundle `.pem` file we get.
    > To verify cert gets installed properly on Linux, you can try use `curl -v https://sitewithsslissue.com` and `pwsh -Command \"Invoke-WebRequest -Uri https://sitewithsslissue.com\"`

### Trust CA certificate for Git CLI

Git uses various CA bundle file depends on your operation system.
- Git packaged the CA bundle file within the Git installation on Windows
- Git use OpenSSL certificate CA bundle file on Linux and macOS

You can check where Git check CA file by running:
```bash
export GIT_CURL_VERBOSE=1
git ls-remote https://github.com/actions/runner HEAD
```

You should see something like:
```
* Couldn't find host github.com in the .netrc file; using defaults
*   Trying 140.82.114.4...
* TCP_NODELAY set
* Connected to github.com (140.82.114.4) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
```
This tells me `/etc/ssl/cert.pem` is where it read trusted CA certificates.

To let Git trusts your CA certificate, you will need to:
1. Save your SSL certificate chain which includes the root CA and all intermediate CAs into a `.pem` file.
2. Set `http.sslCAInfo` Git config or `GIT_SSL_CAINFO` environment variable to the full path of the `.pem` file [Git Doc](https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpsslCAInfo)
> I would recommend using `http.sslCAInfo` since it can be scope to certain hosts that need the extra trusted CA.  
> Ex: `git config --global http.https://myghes.com/.sslCAInfo /extra/ca/cert.pem`  
> This will make Git use the `/extra/ca/cert.pem` only when communicates with `https://myghes.com` and keep using the default CA bundle with others.

### Trust CA certificate for Node.js

Node.js has compiled a snapshot of the Mozilla CA store that is fixed at each version of Node.js' release time.

To let Node.js trusts your CA certificate, you will need to:
1. Save your SSL certificate chain which includes the root CA and all intermediate CAs into a `.pem` file.
2. Set environment variable `NODE_EXTRA_CA_CERTS` which point to the file. ex: `export NODE_EXTRA_CA_CERTS=/full/path/to/cacert.pem` or `set NODE_EXTRA_CA_CERTS=C:\full\path\to\cacert.pem`

---

### Contribute Vscode (`docs/contribute/vscode.md`)

# Development Life Cycle using VS Code:

These examples use VS Code, but the idea should be similar across all IDEs as long as you attach to the same processes in the right folder.
## Configure

To successfully start the runner, you need to register it using a repository and a runner registration token.
Run `Configure` first to build the source code and set up the runner in `_layout`.
Once it's done creating `_layout`, it asks for the url of your repository and your token in the terminal.

Check [Quickstart](../contribute.md#quickstart-run-a-job-from-a-real-repository) if you don't know how to get this token.

## Debugging

Debugging the full lifecycle of a job can be tricky, because there are multiple processes involved.
All the configs below can be found in `.vscode/launch.json`.

## Debug the Listener

```json
{
    "name": "Run [build]",
    "type": "coreclr",
    "request": "launch",
    "preLaunchTask": "build runner layout",  // use the config called "Run" to launch without rebuild
    "program": "${workspaceFolder}/_layout/bin/Runner.Listener",
    "args": [
        "run" // run without args to print usage
    ],
    "cwd": "${workspaceFolder}/src",
    "console": "integratedTerminal",
    "requireExactSource": false,
}
```

If you launch `Run` or `Run [build]`, it starts a process called `Runner.Listener`.
This process will receive any job queued on this repository if the job runs on matching labels (e.g `runs-on: self-hosted`).
Once a job is received, a `Runner.Listener` starts a new process of `Runner.Worker`.
Since this is a different process, you can't use the same debugger session debug it.
Instead, a parallel debugging session has to be started, using a different launch config.
Luckily, VS Code supports multiple parallel debugging sessions.

## Debug the Worker

Because the worker process is usually started by the listener instead of an IDE, debugging it from start to finish can be tricky.
For this reason, `Runner.Worker` can be configured to wait for a debugger to be attached before it begins any actual work.

Set the environment variable `GITHUB_ACTIONS_RUNNER_ATTACH_DEBUGGER` to `true` or `1` to enable this wait.
All worker processes now will wait 20 seconds before they start working on their task.

This gives enough time to attach a debugger by running `Debug Worker`.
If for some reason you have multiple workers running, run the launch config `Attach` instead.
Select `Runner.Worker` from the running processes when VS Code prompts for it.

---

### Design Auth (`docs/design/auth.md`)

# Runner Authentication and Authorization

## Goals
  - Support runner installs in untrusted domains.
  - The account that configures or runs the runner process is not relevant for accessing GitHub resources.
  - Accessing GitHub resources is done with a per-job token which expires when job completes.
  - The token is granted to trusted parts of the system including the runner, actions and script steps specified by the workflow author as trusted.
  - All OAuth tokens that come from the Token Service that the runner uses to access Actions Service resources are the same. It's just the scope and expiration of the token that may vary.

## Configuration

Configuring a self-hosted runner is [covered here in the documentation](https://help.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners).

Configuration is done with the user being authenticated via a time-limited, GitHub runner registration token.

*Your credentials are never used for registering the runner with the service.*



During configuration, an RSA public/private key pair is created, the private key is stored in file on disk. On Windows, the content is protected with DPAPI (machine level encrypted - runner only valid on that machine) and on Linux/OSX with `chmod` permissions.

Using your credentials, the runner is registered with the service by sending the public key to the service which adds that runner to the pool and stores the public key, the Token Service will generate a `clientId` associated with the public key.

## Start and Listen

After configuring the runner, the runner can be started interactively (`./run.cmd` or `./run.sh`) or as a service.



On start, the runner listener process loads the RSA private key (on Windows decrypting with machine key DPAPI), and asks the Token Service for an OAuth token which is signed with the RSA private key.
The server then responds with an OAuth token that grants permission to access the message queue (HTTP long poll), allowing the runner to acquire the messages it will eventually run.

## Run a workflow

When a workflow is run, its labels are evaluated, it is matched to a runner and a message is placed in a queue of messages for that runner.  
The runner then starts listening for jobs via the message queue HTTP long poll.  
The message is encrypted with the runner's public key, stored during runner configuration.  



A workflow is queued as a result of a triggered [event](https://help.github.com/en/actions/reference/events-that-trigger-workflows). Workflows can be scheduled to [run at specific UTC times](https://help.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule) using POSIX `cron` syntax.
An [OAuth token](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) is generated, granting limited access to the host in Actions Service associated with the github.com repository/organization.
The lifetime of the OAuth token is the lifetime of the run or at most the [job timeout (default: 6 hours)](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes), plus 10 additional minutes.

## Accessing GitHub resources

The job message sent to the runner contains the OAuth token to talk back to the Actions Service.
The runner listener parent process will spawn a runner worker process for that job and send it the job message over IPC.
The token is never persisted.

Each action is run as a unique subprocess.
The encrypted access token will be provided as an environment variable in each action subprocess.  
The token is registered with the runner as a secret and scrubbed from the logs as they are written.

Authentication in a workflow run to github.com can be accomplished by using the [`GITHUB_TOKEN`](https://help.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#about-the-github_token-secret)) secret. This token expires after 60 minutes. Please note that this token is different from the OAuth token that the runner uses to talk to the Actions Service.

## Hosted runner authentication

Hosted runner authentication differs from self-hosted authentication in that runners do not undergo a registration process, but instead, the hosted runners get the OAuth token directly by reading the `.credentials` file. The scope of this particular token is limited for a given workflow job execution, and the token is revoked as soon as the job is finished.

---

### Res Runner Auth Diags (`docs/res/runner-auth-diags.txt`)

# Markup used to generate the runner auth diagrams: https://websequencediagrams.com

title Runner Configuration (self-hosted only)

note left of Runner: GitHub repo URL as input
Runner->github.com: Retrieve Actions Service access using runner registration token
github.com->Runner: Access token for Actions Service
note left of Runner: Generate RSA key pair
note left of Runner: Store encrypted RSA private key on disk
Runner->Actions Service: Register runner using Actions Service access token
note right of Runner: Runner name, RSA public key sent
note right of Actions Service: Public key stored
Actions Service->Token Service: Register runner as an app along with the RSA public key
note right of Token Service: Public key stored
Token Service->Actions Service: Client Id for the runner application
Actions Service->Runner: Client Id and Token Endpoint URL
note left of Runner: Store runner configuration info into .runner file
note left of Runner: Store Token registration info into .credentials file

title Runner Start and Running (self-hosted only)

Runner.Listener->Runner.Listener: Start
note left of Runner.Listener: Load config info from .runner
note left of Runner.Listener: Load token registration from .credentials
Runner.Listener->Token Service: Exchange OAuth token (happens every 50 mins)
note right of Runner.Listener: Construct JWT token, use Client Id signed by RSA private key
note left of Actions Service: Find corresponding RSA public key, use Client Id\nVerify JWT token's signature
Token Service->Runner.Listener: OAuth token with limited permission and valid for 50 mins
Runner.Listener->Actions Service: Connect to Actions Service with OAuth token
Actions Service->Runner.Listener: Workflow job

title Running workflow

Runner.Listener->Service (Message Queue): Get message
note right of Runner.Listener: Authenticate with exchanged OAuth token
Event->Actions Service: Queue workflow
Actions Service->Actions Service: Generate OAuth token per job
Actions Service->Actions Service: Build job message with the OAuth token
Actions Service->Actions Service: Encrypt job message with the target runner's public key
Actions Service->Service (Message Queue): Send encrypted job message to runner
Service (Message Queue)->Runner.Listener: Send job
note right of Runner.Listener: Decrypt message with runner's private key
Runner.Listener->Runner.Worker: Create worker process per job and run the job

title Runner Configuration, Start and Running (hosted only)

Machine Management Service->Runner.Listener: Construct .runner configuration file, store token in .credentials
Runner.Listener->Runner.Listener: Start
note left of Runner.Listener: Load config info from .runner
note left of Runner.Listener: Load OAuth token from .credentials
Runner.Listener->Actions Service: Connect to Actions Service with OAuth token in .credentials
Actions Service->Runner.Listener: Workflow job

---

### Start Envlinux (`docs/start/envlinux.md`)

#  Linux System Prerequisites

## Supported Distributions and Versions

Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#linux)."

## Install .Net Core 3.x Linux Dependencies

The [config.sh](../../src/Misc/layoutroot/config.sh) will check .Net Core 3.x dependencies during runner configuration.  
You might see something like this which indicate a dependency's missing.
```bash
./config.sh
    libunwind.so.8 => not found
    libunwind-x86_64.so.8 => not found
Dependencies is missing for Dotnet Core 6.0
Execute ./bin/installdependencies.sh to install any missing Dotnet Core 6.0 dependencies.
```
You can easily correct the problem by executing [installdependencies.sh](../../src/Misc/layoutbin/installdependencies.sh).  
The `installdependencies.sh` script should install all required dependencies on all supported Linux versions  
> Note: The `installdependencies.sh` script will try to use the default package management mechanism on your Linux flavor (ex. `yum`/`apt-get`/`apt`).

### Full dependencies list

Debian based OS (Debian, Ubuntu, Linux Mint)

- liblttng-ust1t64, liblttng-ust1 or liblttng-ust0
- libkrb5-3
- zlib1g
- libssl3t64, libssl3, libssl1.1, libssl1.0.2 or libssl1.0.0
- libicu80, libicu79, ..., libicu66, libicu65, libicu63, libicu60, libicu57, libicu55, or libicu52

Fedora based OS (Fedora, Red Hat Enterprise Linux, CentOS, Oracle Linux 7)

- lttng-ust
- openssl-libs
- krb5-libs
- zlib
- libicu

SUSE based OS (OpenSUSE, SUSE Enterprise)

- lttng-ust
- libopenssl1_1
- krb5
- zlib
- libicu60_2

## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=netcore2x)

---

### Start Envosx (`docs/start/envosx.md`)

#  macOS/OS X System Prerequisites

## Supported Versions

Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#macos)."

## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)

---

### Start Envwin (`docs/start/envwin.md`)

#  Windows System Prerequisites

## Supported Versions

Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#windows)."

## [More .NET Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore30)

---

--- METRICS ---
- Files Extracted: 36
- Estimated Token Budget: ~34697 tokens
- Canonical Reference: https://codewiki.google/github.com/actions/runner
