Skip to content

GH-200 · Domain 1: Author and manage workflows · 24% of the exam

Task 1.2: Design and implement workflow structure

Jobs, steps and conditions, job dependencies, workflow commands, service containers, matrices, YAML anchors, contexts and expressions, runner image changes and editor tooling.

Study it

Sample questions

Try each one before opening the answer. Every option is explained, with the GitHub documentation page that proves it.

Question 1 · choose 1

A step base64-encodes the SIGNING_KEY secret to pass it to a tool. A reviewer finds the encoded value in plain text in the run log, while the original secret shows as asterisks. The encoded value must be hidden in every later step too. What should the step do?

  1. AWrite the encoded value to GITHUB_ENV, whose values are redacted in logs
  2. BRegister the encoded value with ::add-mask:: before using it
  3. CStore the encoded form as a second repository secret in advance
  4. DTurn off step debug logging for the repository
Show the answer and why
  • AWrite the encoded value to GITHUB_ENV, whose values are redacted in logs

    Incorrect

    Environment file values aren't redacted; the encoded string would still appear wherever it is printed.

  • BRegister the encoded value with ::add-mask:: before using it

    Correct

    Masking the transformed value makes the runner redact it in the rest of the job's log output.

  • CStore the encoded form as a second repository secret in advance

    Incorrect

    A secret is redacted only when the job uses it, and this job never references the second secret.

  • DTurn off step debug logging for the repository

    Incorrect

    The value is printed in the normal log, so debug logging isn't the cause.

Redaction relies largely on finding an exact match for a secret's value, so a transformed secret, such as a Base64 or URL-encoded copy, isn't redacted automatically. GitHub's guidance is to register any value generated from a secret, including encodings, with the add-mask workflow command.

Question 2 · choose 1

A team copies a working Ubuntu job that uses a redis service container to a new job with runs-on: windows-latest, to test the Windows build against redis. The Windows job fails while setting up the service. What is the cause?

  1. AThe redis image needs credentials to be pulled on Windows
  2. BWindows jobs must map service ports with the udp suffix
  3. CService containers need a Linux runner
  4. DThe service label must match the image name on Windows runners
Show the answer and why
  • AThe redis image needs credentials to be pulled on Windows

    Incorrect

    Credentials depend on the registry, not the runner's operating system.

  • BWindows jobs must map service ports with the udp suffix

    Incorrect

    The protocol suffix is optional and isn't tied to the operating system.

  • CService containers need a Linux runner

    Correct

    GitHub-hosted service containers and container jobs require a Linux runner with Docker.

  • DThe service label must match the image name on Windows runners

    Incorrect

    The label is any name you choose, on any runner.

Service containers and container jobs require a Linux environment: on GitHub-hosted runners that means an Ubuntu runner, and on self-hosted runners a Linux machine with Docker installed. A Windows test can reach a service that runs elsewhere, but not a service container in the job.

Question 3 · choose 1

In a monorepo, a detect job writes the changed package names to its output packages as a JSON array, such as ["api","web"]. A test job must run once per changed package, and the list differs on every push. How should the test job define its matrix?

  1. Apackage: ${{ needs.detect.outputs.packages }}
  2. Bpackage: ${{ steps.detect.outputs.packages }}, reading the step that wrote the list
  3. Cpackage: ${{ fromJSON(needs.detect.outputs.packages) }}
  4. Dpackage: ${{ env.PACKAGES }}, after detect writes the list to GITHUB_ENV
Show the answer and why
  • Apackage: ${{ needs.detect.outputs.packages }}

    Incorrect

    Outputs are strings, so the matrix would get text instead of an array of package names.

  • Bpackage: ${{ steps.detect.outputs.packages }}, reading the step that wrote the list

    Incorrect

    The steps context isn't available in strategy, and the step belongs to another job.

  • Cpackage: ${{ fromJSON(needs.detect.outputs.packages) }}

    Correct

    fromJSON turns the job output into an array, so one job runs for each package.

  • Dpackage: ${{ env.PACKAGES }}, after detect writes the list to GITHUB_ENV

    Incorrect

    Environment file values stay in the job that wrote them, and env isn't available in strategy.

A matrix can be built at run time from a previous job's output. Job outputs are strings, so the value is passed through fromJSON to become an array or object. The strategy key can use only the github, needs, vars and inputs contexts, which is why the list has to come through needs.

Question 4 · choose 1

A compatibility workflow builds a matrix of 3 operating systems, 10 runtime versions and 9 regions, 270 combinations in one job. What happens when it runs?

  1. AThe matrix exceeds the limit of 256 jobs per workflow run
  2. BAll 270 jobs run, but only 20 at a time on a Free plan
  3. CThe matrix runs if max-parallel is set below 256
  4. DThe jobs run on self-hosted runners, where the limit doesn't apply
Show the answer and why
  • AThe matrix exceeds the limit of 256 jobs per workflow run

    Correct

    A job matrix can generate at most 256 jobs per run, on any kind of runner.

  • BAll 270 jobs run, but only 20 at a time on a Free plan

    Incorrect

    Concurrency limits queue jobs, but this matrix is larger than any run may generate.

  • CThe matrix runs if max-parallel is set below 256

    Incorrect

    max-parallel limits how many jobs run at once, not how many the matrix generates.

  • DThe jobs run on self-hosted runners, where the limit doesn't apply

    Incorrect

    The matrix limit applies to both GitHub-hosted and self-hosted runners.

A job matrix can generate a maximum of 256 jobs per workflow run, and the limit applies to GitHub-hosted and self-hosted runners alike. Larger test sets must be split, for example into several matrix jobs or workflows.

Question 5 · choose 1

Platform engineers want to move every job of a workflow to a different runner label without editing the file, by changing one value in the repository settings. A first attempt, runs-on: ${{ env.RUNNER_LABEL }} with RUNNER_LABEL in the workflow's env, fails validation. What should runs-on use?

  1. A${{ vars.RUNNER_LABEL }}, a repository configuration variable
  2. B${{ secrets.RUNNER_LABEL }}, a repository secret
  3. C${{ env.RUNNER_LABEL }} with the variable set in the job's env instead
  4. D${{ steps.pick.outputs.label }} from a first step that reads the settings
Show the answer and why
  • A${{ vars.RUNNER_LABEL }}, a repository configuration variable

    Correct

    runs-on can use the vars context, and the value is changed in the repository settings.

  • B${{ secrets.RUNNER_LABEL }}, a repository secret

    Incorrect

    The secrets context isn't available in runs-on.

  • C${{ env.RUNNER_LABEL }} with the variable set in the job's env instead

    Incorrect

    The env context isn't available in runs-on at any level.

  • D${{ steps.pick.outputs.label }} from a first step that reads the settings

    Incorrect

    Steps run after the runner is chosen, and the steps context isn't available in runs-on.

jobs.<job_id>.runs-on can use the github, needs, strategy, matrix, vars and inputs contexts. Configuration variables are read through vars, so changing the repository variable in settings moves the jobs without editing the workflow.

Question 6 · choose 1

Every run step in a job starts with cd frontend. How can the job set that directory once for all of its run steps?

  1. Aenv: { PWD: frontend } on the job
  2. Bdefaults: run: working-directory: frontend on the job
  3. CA first step that runs cd frontend
  4. DA with: working-directory input on actions/checkout for each job
Show the answer and why
  • Aenv: { PWD: frontend } on the job

    Incorrect

    Setting a PWD variable doesn't change the directory a shell starts in.

  • Bdefaults: run: working-directory: frontend on the job

    Correct

    Job-level defaults apply the working directory to every run step in the job.

  • CA first step that runs cd frontend

    Incorrect

    Each run step starts a new shell, so a cd doesn't carry over.

  • DA with: working-directory input on actions/checkout for each job

    Incorrect

    That input doesn't exist on checkout, and it wouldn't affect run steps.

defaults.run sets the default shell and working-directory for all run steps, at the workflow level or for a single job. Each run step starts its own shell, so changing directory in one step doesn't affect the next.

Question 7 · choose 1

A build script prints hundreds of dependency lines that clutter the run log. The team wants them folded under one expandable heading. Which workflow commands should the script print around them?

  1. A::stop-commands:: with a token before the lines
  2. B::notice:: on each line
  3. C::group::Dependencies and ::endgroup::
  4. D::add-mask:: on each line
Show the answer and why
  • A::stop-commands:: with a token before the lines

    Incorrect

    That stops processing of workflow commands; it doesn't fold lines.

  • B::notice:: on each line

    Incorrect

    Every line would become an annotation, adding clutter.

  • C::group::Dependencies and ::endgroup::

    Correct

    Lines between these commands fold into one expandable group in the log.

  • D::add-mask:: on each line

    Incorrect

    Masking hides values as asterisks; the lines would still fill the log.

The group and endgroup workflow commands create an expandable group in the log. stop-commands pauses command processing, notice creates annotations, and add-mask redacts values.

Question 8 · choose 1

A step installs a CLI into $HOME/tools/bin. Later steps in the job must be able to call it by name. What should the installing step do?

  1. Aexport PATH="$HOME/tools/bin:$PATH"
  2. BWrite PATH_EXTRA=$HOME/tools/bin to GITHUB_OUTPUT
  3. CAppend $HOME/tools/bin to the GITHUB_PATH file
  4. DSet PATH in the workflow's top-level env
Show the answer and why
  • Aexport PATH="$HOME/tools/bin:$PATH"

    Incorrect

    An export lasts only for that step's shell.

  • BWrite PATH_EXTRA=$HOME/tools/bin to GITHUB_OUTPUT

    Incorrect

    An output is read through the steps context; it doesn't change PATH.

  • CAppend $HOME/tools/bin to the GITHUB_PATH file

    Correct

    Paths added there are prepended to PATH for all later steps in the job.

  • DSet PATH in the workflow's top-level env

    Incorrect

    A fixed value there would replace the runner's PATH instead of adding to it.

Writing a directory to the GITHUB_PATH environment file prepends it to the system PATH for all subsequent steps and actions in the current job; the step that writes it doesn't see the change.

Question 9 · choose 1

A job should run only when the pull request that triggered it has the label deploy-preview, among any others. Which condition fits?

  1. Acontains(github.event.pull_request.labels.*.name, 'deploy-preview')
  2. Bgithub.event.pull_request.labels == 'deploy-preview'
  3. CstartsWith(github.event.pull_request.labels, 'deploy-preview')
  4. Dgithub.event.label.name == 'deploy-preview' on every pull request event
Show the answer and why
  • Acontains(github.event.pull_request.labels.*.name, 'deploy-preview')

    Correct

    The object filter returns the label names as an array, and contains checks for the element.

  • Bgithub.event.pull_request.labels == 'deploy-preview'

    Incorrect

    labels is an array of objects, so comparing it with a string is never true.

  • CstartsWith(github.event.pull_request.labels, 'deploy-preview')

    Incorrect

    startsWith works on strings, not on an array of label objects.

  • Dgithub.event.label.name == 'deploy-preview' on every pull request event

    Incorrect

    That property is set only for labeled events and names the one label just added.

The * object filter selects a property from each item in a collection, so labels.*.name returns an array of label names. contains() returns true if an array includes the item. The label property of the payload exists only for labeled and unlabeled activity.

Practice domain 1 →Practice all domains →