Free GitHub Actions GH-200 Practice Questions: Author and Maintain Actions
Practice 10 free GitHub Actions (GitHub Actions GH-200) questions on Author and Maintain Actions, with answers, explanations, and the IT Mastery next step.
Try the IT Mastery web app for a richer interactive practice experience with mixed sets, timed mocks, topic drills, explanations, and progress tracking.
Topic snapshot
| Field | Detail |
|---|---|
| Practice target | GitHub Actions GH-200 |
| Topic area | Author and Maintain Actions |
| Blueprint weight | 18% |
| Page purpose | Focused sample questions before returning to mixed practice |
How to use this topic drill
Use this page to isolate Author and Maintain Actions for GitHub Actions GH-200. Work through the 10 questions first, then review the explanations and return to mixed practice in IT Mastery.
| Pass | What to do | What to record |
|---|---|---|
| First attempt | Answer without checking the explanation first. | The fact, rule, calculation, or judgment point that controlled your answer. |
| Review | Read the explanation even when you were correct. | Why the best answer is stronger than the closest distractor. |
| Repair | Repeat only missed or uncertain items after a short break. | The pattern behind misses, not the answer letter. |
| Transfer | Return to mixed practice once the topic feels stable. | Whether the same skill holds up when the topic is no longer obvious. |
Blueprint context: 18% of the practice outline. A focused topic score can overstate readiness if you recognize the pattern too quickly, so use it as repair work before timed mixed sets.
Sample questions
These are original IT Mastery practice questions aligned to this topic area. They are not official GitHub questions, copied live-exam content, or exam dumps. Use them to preview question style and explanation depth before continuing with topic drills, mixed sets, and timed mocks in IT Mastery.
Question 1
Topic: Author and Maintain Actions
An organization has 60 private repositories that all need the same internally supported signing logic. Each team must insert that logic at different points in different jobs, security prohibits public distribution, and the platform team wants one codebase with independent versioning and centralized support. Which configuration is best?
Options:
A. Move the logic into a central reusable workflow that every repository calls.
B. Create a dedicated private action repository and let organization repositories consume versioned releases from it.
C. Publish the action from a public repository to GitHub Marketplace.
D. Copy the composite action into each consuming repository.
Best answer: B
Explanation: A dedicated private action repository best fits because the shared logic is a reusable step sequence, not an entire standardized job. It also keeps the action internal while giving the platform team one supported codebase with its own release lifecycle.
Choose the distribution boundary that matches both the reuse unit and the trust boundary. Here, teams need the same logic inserted at different places inside different jobs, so an action is the right reusable component. Because many private repositories will use it and the platform team wants separate ownership, support, and versioning, that action should live in its own private repository rather than inside each application repository.
A public Marketplace publication would break the internal-only requirement. A reusable workflow is better when you want to standardize an entire job or workflow-call boundary, not when teams need a shared step sequence they can place flexibly. The key takeaway is to use a private, centrally maintained action repository when reuse spans many repositories but must remain internal.
- Copying the action into every repository removes centralized maintenance and makes support and version control inconsistent.
- Publishing through a public repository or Marketplace violates the stated internal-only security boundary.
- Using a reusable workflow is too coarse when teams need to place shared logic at different step positions within different jobs.
Question 2
Topic: Author and Maintain Actions
A team maintains a local composite action that should make a CLI available to later workflow steps.
# .github/actions/setup-tool/action.yml
runs:
using: composite
steps:
- shell: bash
run: |
TOOL_DIR="$GITHUB_ACTION_PATH/bin"
export TOOL_HOME="$TOOL_DIR"
export PATH="$TOOL_DIR:$PATH"
# .github/workflows/build.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: ./.github/actions/setup-tool
- run: |
echo "$TOOL_HOME"
mytool --version
The second step prints an empty TOOL_HOME and mytool: command not found. Which edit best fixes the action safely so later steps in the same job can use both values?
Options:
A. Replace the
exportlines with::set-envand::add-pathworkflow commands.B. Replace the
exportlines by writingTOOL_HOMEtoGITHUB_ENVandTOOL_DIRtoGITHUB_PATH.C. Keep the action unchanged and add an
env:block on theuses: ./.github/actions/setup-toolstep.D. Replace the
exportlines by writing both values toGITHUB_OUTPUTso following steps inherit them automatically.
Best answer: B
Explanation: The action fails because export only changes the environment of that single shell process. To make an environment variable and a PATH entry available to later steps in the same job, the action must write to the supported environment files: GITHUB_ENV and GITHUB_PATH.
The key concept is step scope. In a composite action, export TOOL_HOME=... and export PATH=... affect only the shell running that one run step. When the workflow moves to the next step, GitHub Actions starts a new process, so those exported values are gone.
- shell: bash
run: |
TOOL_DIR="$GITHUB_ACTION_PATH/bin"
echo "TOOL_HOME=$TOOL_DIR" >> "$GITHUB_ENV"
echo "$TOOL_DIR" >> "$GITHUB_PATH"
GITHUB_ENV makes TOOL_HOME available to later steps in the same job, and GITHUB_PATH safely adds the tool directory to command lookup. Using step outputs would require explicit consumption and still would not update PATH automatically.
GITHUB_OUTPUTcreates step outputs, not automatic job-wide environment variables or PATH updates.- An
env:block on theusesstep applies to that step invocation and does not make later workflow steps inherit the action’s exported shell state. ::set-envand::add-pathare deprecated; environment files are the supported safe mechanism.
Question 3
Topic: Author and Maintain Actions
Your JavaScript action is still on major version 1, and consumers use uses: contoso/cache-action@v1. The team sometimes pushes prerelease tags such as v1.5.0-rc.1 before publishing a GitHub release. The v1 tag must move only after a non-prerelease release is published.
name: update-major
on:
push:
tags:
- 'v1.*'
jobs:
move-tag:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- run: |
git tag -fa v1 -m "Update v1"
git push origin refs/tags/v1 --force
Which edit best satisfies the requirement?
Options:
A. Keep the tag-push trigger, but add
if: ${{ !contains(github.ref_name, '-') }}before updatingv1.B. Change the trigger to
releasetypes: [created]sov1moves when the release entry is first made.C. Replace the trigger with
releasetypes: [published], addif: ${{ !github.event.release.prerelease }}, and checkout${{ github.event.release.tag_name }}before updatingv1.D. Change the trigger to
releasetypes: [published], but checkoutref: mainbefore updatingv1.
Best answer: C
Explanation: A floating major tag such as v1 should advance only when the stable release is actually published, not when any matching tag is pushed. Using release with types: [published], checking github.event.release.prerelease, and checking out the released tag keeps v1 aligned to the correct backward-compatible release commit.
Major-version tags for custom actions are movable aliases, while full semantic version tags such as v1.4.2 should remain fixed release points. In this workflow, push on tags is too early because a matching tag can be pushed before the GitHub release is published, and prerelease tags can match the same pattern.
The safe pattern is:
- trigger on
releasewithtypes: [published] - skip the job when
github.event.release.prereleaseistrue - checkout the released tag with
${{ github.event.release.tag_name }} - force-move
v1to that released commit
The existing contents: write permission is what allows the workflow to update the tag. Checking out main would make v1 follow a branch tip instead of the immutable commit associated with the published release. The key point is that the floating major tag should follow the published stable release, not tag pushes or branch heads.
- Tag-push guard still runs before a GitHub release is published, so it can move
v1too early. - Release created can happen before final publication, including draft creation, so it is not the right release point.
- Checkout main makes
v1follow the latest branch commit rather than the published release tag commit.
Question 4
Topic: Author and Maintain Actions
A team created a local JavaScript action at .github/actions/label-release/ and calls it from a workflow with uses: ./.github/actions/label-release. The job fails before any script output appears.
Exhibit:
# .github/actions/label-release/action.yml
name: Release labeler
description: Adds a label based on release type
inputs:
release-type:
description: Release channel
required: true
outputs:
applied-label:
description: Label added to the issue
Log excerpt:
Error: action metadata is invalid
Required property is missing: runs
What is the best explanation for the failure?
Options:
A. The workflow needs
permissions: issues: writebefore GitHub can load the local action.B. The local action must be stored under
.github/workflowsinstead of.github/actions.C. The action output should be defined only through
GITHUB_ENV, not inaction.yml.D. The action metadata is missing a
runssection that declares the runtime and entry point.
Best answer: D
Explanation: The failure occurs during action metadata validation, not during script execution. For a JavaScript action, action.yml must include a runs block that tells GitHub which runtime to use and which file to execute.
Custom actions need an action.yml (or action.yaml) metadata file. For a JavaScript action, that file can include name, description, inputs, and outputs, but it also must include a runs section so GitHub knows how to execute the action.
A minimal JavaScript action metadata file includes:
runs.usingfor the runtimeruns.mainfor the entry file
Because the log says the required property runs is missing, GitHub stops before the action code starts. Permissions problems or output-writing mistakes would happen later, after the action had already been loaded and executed.
- The option claiming the action belongs under
.github/workflowsis incorrect because that folder is for workflow files, while local actions are commonly stored in paths such as.github/actions. - The option about
issues: writeis a later runtime concern; missing permissions would not cause a metadata validation error aboutruns. - The option about
GITHUB_ENVis incorrect because action outputs are declared in metadata and runtime output values are written withGITHUB_OUTPUT, notGITHUB_ENV.
Question 5
Topic: Author and Maintain Actions
You are authoring a composite action named package-report. The action should behave as follows:
- The caller must provide
report-path. formatis optional and should default tosarif.include-summaryis optional and should default totrue.- Each input needs a description in
action.yml.
Which inputs definition is the best configuration?
Options:
- A. ```yaml inputs: report-path: description: Path to the generated report file required: false default: ./results.sarif format: description: Output format for the published report required: true include-summary: description: Whether to add a workflow summary required: false default: ’true'
- B. ```yaml
inputs:
report-path:
description: Path to the generated report file
required: true
format:
description: Output format for the published report
required: false
default: sarif
include-summary:
description: Whether to add a workflow summary
required: false
default: 'true'
- C. ```yaml inputs: report-path: description: Path to the generated report file required: true format: description: Output format for the published report required: false default: json include-summary: description: Whether to add a workflow summary required: false default: ‘false’
- D. ```yaml
inputs:
report-path:
description: Path to the generated report file
required: true
format:
description: Output format for the published report
required: true
default: sarif
include-summary:
description: Whether to add a workflow summary
required: true
default: 'true'
Best answer: B
Explanation: The best metadata definition matches the action contract exactly: one required caller-supplied input and two optional inputs with explicit defaults. That keeps the action interface clear and prevents callers from being forced to pass values the action already knows how to default.
In action.yml, input metadata should mirror how the action is meant to be used. A value the caller must always supply should be marked required: true and normally should not rely on a fallback. Optional behavior should be marked required: false and given a default when the action has a known fallback value.
Here, report-path must come from the caller, so it is the only required input. format and include-summary are optional because the action is supposed to use sarif and true when the caller omits them. Including descriptions for each input makes the contract clear to anyone consuming the action.
The closest distractors either change which values are required or change the defaults, which means the metadata no longer matches the stated behavior.
- The option that makes
report-pathoptional changes the contract because the caller is no longer required to supply the file path. - The option that marks
formatandinclude-summaryas required contradicts the requirement that both are optional with defaults. - The option that uses
jsonand'false'changes the declared fallback behavior of the action.
Question 6
Topic: Author and Maintain Actions
A team rewrote a composite action so invalid caller inputs fail with targeted guidance. The action defines environment as required. approver, token, and use_oidc are optional, and omitted optional inputs resolve to empty strings. In this run, secrets.DEPLOY_TOKEN exists.
# action.yml (fragment)
runs:
using: composite
steps:
- shell: bash
env:
ENVIRONMENT: ${{ inputs.environment }}
APPROVER: ${{ inputs.approver }}
USE_OIDC: ${{ inputs.use_oidc }}
TOKEN: ${{ inputs.token }}
run: |
if [[ "$ENVIRONMENT" == "prod" && -z "$APPROVER" ]]; then
echo "::error title=Invalid action configuration::For production, set input 'approver' in the calling workflow."
exit 1
fi
if [[ "$USE_OIDC" == "true" && -n "$TOKEN" ]]; then
echo "::error title=Invalid action configuration::Use either 'token' or 'use_oidc: true', not both."
exit 1
fi
# caller step
- uses: org/deploy-action@v1
with:
environment: prod
use_oidc: 'true'
token: ${{ secrets.DEPLOY_TOKEN }}
Which message is emitted by the validation step?
Options:
A. For production, set input
approverin the calling workflow.B. Input required and not supplied:
approver.C. No error is emitted; validation passes.
D. Use either
tokenoruse_oidc: true, not both.
Best answer: A
Explanation: The validation logic runs top to bottom. Because the caller sets environment to prod and omits approver, the first condition is true and the script exits immediately with the targeted production guidance.
This item tests how a custom action surfaces a helpful configuration error during execution. The caller has two bad settings, but the script stops at the first failing check. Here, ENVIRONMENT is prod, APPROVER is empty, and the first if emits a message that tells the workflow author exactly what to add in the caller.
ENVIRONMENT=prodAPPROVER=""- first validation branch runs
exit 1stops the step immediately
Because the step exits on that first branch, the later token plus use_oidc conflict is never evaluated.
- The token-and-OIDC conflict is real, but it is checked second and never runs after the first
exit 1. - The missing-required-input message would only occur if
approverwere defined as required in the action metadata. - The no-error option ignores that the first condition is satisfied and explicitly fails the step.
Question 7
Topic: Author and Maintain Actions
A platform team maintains an internal action used as uses: org/release-note@v2. Consuming workflows run on self-hosted Linux runners that have no outbound internet except GitHub and no Docker daemon.
The current action.yml is:
runs:
using: node16
main: src/index.js
The action repo contains package.json, but its npm dependencies are not bundled or committed. Runs now fail with:
Error: Cannot find module '@actions/core'
The node16 runtime is not supported on this runner
The team wants the most reliable configuration with no extra setup in consuming workflows. Which configuration is best?
Options:
A. Convert it to a composite action that runs
node src/index.js.B. Bundle dependencies into
dist, commit it, and usenode20.C. Keep
src/index.jsand addnpm cibefore each use.D. Convert it to a Docker action built from
node:20.
Best answer: B
Explanation: A JavaScript action should ship executable code and dependencies, typically as a bundled dist/index.js, because consuming workflows do not install the action’s npm packages automatically. Updating runs.using to a supported runtime such as node20 fixes the Node compatibility failure at the same time.
This failure has two causes: packaging and runtime selection. For a JavaScript action, the consumer runner does not run npm install for the action repository, so pointing main to src/index.js while leaving dependencies only in package.json can produce Cannot find module errors. The reliable fix is to build or bundle the action and commit the generated output, commonly dist/index.js, then set main to that file.
The second error comes from declaring an unsupported Node runtime in action.yml. Updating runs.using to a supported value such as node20 lets the runner execute the action on current GitHub Actions infrastructure. Requiring each workflow to bootstrap the action, or switching to Docker on runners without Docker, adds fragility instead of fixing the action package itself.
- The workflow bootstrap idea fails because consuming workflows do not normally install dependencies inside the referenced action repository, and the runners cannot fetch arbitrary packages anyway.
- The composite-action approach still relies on
nodeplus the action’s local npm packages being present, so it does not solve the missing dependency problem. - The Docker-action approach could change the runtime model, but it cannot run on the stated runners because Docker is unavailable and it is unnecessary for this scenario.
Question 8
Topic: Author and Maintain Actions
A team is troubleshooting a local composite action used by a matrix job on GitHub-hosted runners. The repository contains .github/actions/pkg/scripts/build.sh, and build.sh has the executable bit committed in Git.
# workflow.yml
jobs:
pack:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/pkg
# .github/actions/pkg/action.yml
name: pkg
runs:
using: composite
steps:
- name: Run build
shell: bash
working-directory: ${{ github.action_path }}\scripts
run: ./build.sh
Which execution trace matches GitHub Actions behavior?
Options:
A. Both runners run
build.shbecause the path is normalized.B. Windows runs
build.sh; Ubuntu fails beforebashstarts.C. Ubuntu runs
build.sh; Windows fails becausebashis unsupported.D. Both runners fail because
build.shstill needschmod +x.
Best answer: B
Explanation: github.action_path resolves to the action folder on each runner, but the appended \scripts hard-codes a Windows path separator. On Ubuntu, the runner cannot resolve that working directory, so the step fails before bash runs. On Windows, the directory is valid and the script can run.
In a composite action, working-directory is resolved by the runner before the run command starts. Here, ${{ github.action_path }} expands to the absolute path of the action directory, but \scripts makes the final path Windows-specific. On Ubuntu, that becomes a path ending in pkg\scripts, which Linux treats as a different name rather than the scripts subfolder, so the runner cannot change into the directory and the step stops before shell execution.
On Windows, the same path is valid. Because the step explicitly uses shell: bash, the Windows GitHub-hosted runner can launch Bash for that step. Since build.sh already has its executable bit committed in Git, there is no extra permission fix required in this scenario.
For cross-platform action authoring, use an OS-neutral path such as ${{ github.action_path }}/scripts.
- Permission trap fails because the script already has the executable bit committed, so no extra
chmod +xstep is required here. - Shell trap fails because GitHub-hosted Windows runners can run steps with
shell: bash. - Normalization trap fails because the runner does not rewrite a Windows-style backslash path into a Linux subdirectory path.
Question 9
Topic: Author and Maintain Actions
Consider this workflow job:
jobs:
lint-notes:
runs-on: ubuntu-latest
steps:
- id: notes
run: |
NOTES=$(cat notes.txt)
echo "::warning::$NOTES"
echo "notes=$NOTES" >> "$GITHUB_OUTPUT"
- run: echo "${{ steps.notes.outputs.notes }}"
notes.txt can contain multiple lines and text such as ::notice::review me. Which edit best preserves the full value for steps.notes.outputs.notes and avoids malformed workflow command parsing?
Options:
A. Replace both lines with
echo "::set-output name=notes::$NOTES".B. Escape
%, CR, and LF before::warning::, and writenotestoGITHUB_OUTPUTwith the multilinenotes<<EOFformat.C. Replace
GITHUB_OUTPUTwithGITHUB_ENVand reference${{ env.notes }}in the next step.D. Keep
::warning::unchanged, but write the output withprintf 'notes=%s\n' "$NOTES" >> "$GITHUB_OUTPUT".
Best answer: B
Explanation: The value is unsafe in two different ways: annotation commands parse special characters, and GITHUB_OUTPUT single-line syntax does not safely carry multiline data. The fix is to escape the warning message and use the multiline environment-file form for the step output.
GitHub Actions still parses lines such as ::warning::... as workflow commands, so arbitrary text inserted into that command must escape %, carriage returns, and newlines. Otherwise, the runner can misread the message or treat later lines as separate commands. For step outputs, the current pattern is the GITHUB_OUTPUT file, and multiline values must use the delimiter form rather than name=value.
A safe pattern is:
esc=${NOTES//'%'/'%25'}
esc=${esc//$'\n'/'%0A'}
esc=${esc//$'\r'/'%0D'}
echo "::warning::$esc"
{
echo 'notes<<EOF'
printf '%s\n' "$NOTES"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
Using GITHUB_ENV changes the data to an environment variable for later steps in the same job, while ::set-output is deprecated and should not be used.
- Single-line output write fails because
printf 'notes=%s\n'still produces invalid multiline output when the value itself contains line breaks. - Using
GITHUB_ENVchanges the mechanism; it does not createsteps.notes.outputs.notesfor step-output references. - Deprecated command is wrong because
::set-outputis obsolete and still relies on workflow-command parsing instead of the recommended environment file.
Question 10
Topic: Author and Maintain Actions
A workflow generates Markdown release notes and needs them as an environment variable in a later step of the same job. The notes can contain blank lines.
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Generate notes
run: |
NOTES=$(printf 'Fix login bug\n\n- add tests\n- update docs\n')
echo "RELEASE_NOTES=$NOTES" >> "$GITHUB_ENV"
- name: Use notes
run: printf '%s\n' "$RELEASE_NOTES"
Which edit best fixes this so the full multiline value is available in Use notes?
Options:
A. Move
RELEASE_NOTESto the job-levelenv:block.B. Write the notes to
GITHUB_STEP_SUMMARYand read them later.C. Write
RELEASE_NOTEStoGITHUB_ENVwith the<<DELIMITERmultiline format.D. Use
export RELEASE_NOTES="$NOTES"in the first step instead.
Best answer: C
Explanation: GITHUB_ENV can pass values to later steps in the same job, but raw NAME=value syntax only works for single-line content. For multiline data, you must use the NAME<<DELIMITER format so GitHub Actions treats the entire block as one value.
When a step writes to GITHUB_ENV, GitHub Actions parses that file as environment-file commands. A line like RELEASE_NOTES=$NOTES is valid only if the value stays on one line. If the value contains embedded newlines, those extra lines are parsed separately, which breaks the environment file or loses part of the content.
{
echo 'RELEASE_NOTES<<EOF'
printf '%s\n' "$NOTES"
echo 'EOF'
} >> "$GITHUB_ENV"
This delimiter form tells Actions to read everything until the closing token as one multiline value. Choose a delimiter that will not appear by itself in the content. The variable then becomes available to later steps in the same job, which is exactly what this workflow needs.
- Job-level
env:does not solve runtime generation, because the value is being created inside the step’s shell. - Shell
exportonly affects the current step process and does not persist to later steps. GITHUB_STEP_SUMMARYis for run summary output, not for creating environment variables for subsequent steps.
Continue in the web app
Use IT Mastery for interactive GitHub Actions GH-200 practice with mixed sets, timed mocks, topic drills, explanations, and progress tracking.
Try GitHub Actions GH-200 on Web