GitHub Actions Interview Questions — Practice CI/CD Quiz
Reviewed by Mark Dickie · Last updated
13 questions on this page you can answer and see marked on the spot. Go to the first one
GitHub Actions is a CI/CD platform built into GitHub that automates build, test, and deployment workflows through YAML files stored in your repository. For interviews, expect questions on workflow syntax, runner types, secret management, caching strategies, and how to compose reusable workflows. You should also know the difference between actions and steps, how needs controls job ordering, and how expressions and contexts like ${{ github.event_name }} let you conditionally run jobs.
What does a GitHub Actions interview typically cover?
Interviews on GitHub Actions usually split into a few areas: workflow structure, runner configuration, security, and optimization. The table below maps the most commonly asked topics to what you need to know for each.
| Topic | What to know |
|---|---|
| Workflow triggers | push, pull_request, schedule, workflow_dispatch, repository_dispatch, and path/branch filtering |
| Jobs and steps | runs-on, needs for ordering, if conditionals, continue-on-error, and step-level run vs uses |
| Runners | GitHub-hosted (Ubuntu/Windows/macOS) vs self-hosted, labels, and runner groups |
| Secrets and variables | Repository, environment, and organization secrets; secrets context; masked output |
| Caching | actions/cache, key patterns, restore keys, and cache scope per branch |
| Reusable workflows | workflow_call trigger, inputs/outputs, and calling with uses: ./.github/workflows/... |
| Security | GITHUB_TOKEN permissions, environment protection rules, and OIDC federation for cloud deployments |
How should I prepare for a GitHub Actions interview?
- Learn the full workflow YAML schema —
name,on,jobs,steps, and the contexts (github,env,secrets,matrix). - Practice writing a workflow from scratch that runs tests on push, caches dependencies, and deploys on a tagged release.
- Study the
actions/cacheaction and understand cache key strategies for monorepo and multi-language projects. - Review security:
GITHUB_TOKENdefault vs explicit permissions, environment secrets vs repository secrets, and OIDC for cloud auth. - Get comfortable with matrix builds,
fail-fast, andmax-parallelto control how many jobs run at once. - Read up on reusable workflows and composite actions — interviewers like to ask when you would choose one over the other.
Key facts
- Tarmac has 167 GitHub Actions interview questions on this topic, 25 of them on this page, at difficulty 1–5 of 5.
- Tarmac tracked 473 job postings asking for GitHub Actions in September 2026.
- Roles asking for GitHub Actions advertise a median base salary of £67,500, across 87 job postings as of September 2026.
- Tarmac last reviewed these GitHub Actions interview questions on 20 October 2026.
At a glance
| Questions | 25 shown · 167 in the bank |
|---|---|
| Difficulty | 1–5 of 5 |
| Formats | True / false, Multiple choice, Ordering, Short answer, Multiple answer, Fill in the blank, Flashcard, Design exercise |
What you'll review
- reusable workflows gha
- composite actions gha
- using marketplace actions
Practice questions
Try one before you open the answer. Pick an option and press Check; it's marked on the spot.
In GitHub Actions, a workflow must declare the workflow_call event in its on configuration for it to be callable as a reusable workflow by other workflows.#
Options
Show answer
True. A workflow must include the workflow_call event in its on configuration to be callable as a reusable workflow. Without this trigger, GitHub Actions does not expose the workflow for reuse by other workflows via the uses keyword.
The workflow_call trigger is what designates a workflow as reusable. Without it in the on key, no other workflow can reference it via the uses keyword — GitHub Actions simply will not expose it as a callable workflow.
In GitHub Actions, a calling workflow invokes a reusable workflow by setting the needs keyword to the target workflow's file name (for example, needs: .github/workflows/deploy.yml).#
Options
Show answer
False. A calling workflow invokes a reusable workflow by specifying the uses key inside a job definition (for example, uses: ./.github/workflows/deploy.yml). The needs keyword declares dependencies between jobs within the same workflow, not calls to external workflow files.
Reusable workflows are invoked through the uses key inside a job definition (e.g. uses: ./.github/workflows/deploy.yml), not through needs. The needs keyword defines dependencies between jobs within the same workflow, not calls to other workflow files.
In a composite action's action.yml metadata file, which property and value declare that the action is a composite (multi-step) action?#
Options
Show answer
A composite action is declared in its action.yml file by setting runs.using: composite. This tells GitHub Actions that the action bundles a list of steps defined directly in the metadata, rather than running a Docker container or a JavaScript entry point.
A composite action is declared in its action.yml file with runs.using: composite. The runs key is the standard metadata block for how an action executes, and composite is the recognized value that tells GitHub Actions to treat the bundled steps array as the action's implementation rather than running a Docker container or a single JavaScript/TypeScript entry point.
Where are the individual steps of a composite action defined?#
Options
Show answer
The steps of a composite action are defined in a steps array nested under the runs key inside action.yml. Each entry is a step object with fields like name, run, uses, and with, mirroring the step syntax used in normal workflow jobs.
Composite actions define their steps in a steps array nested under the runs key inside action.yml. Each entry in this array is a step object that can specify name, run, uses, with, shell, and similar fields — mirroring the step syntax used in workflow job definitions. There is no jobs key (actions are invoked from workflow jobs, they don't define their own), and there is no separate steps file mechanism.
Inside a composite action, how do you reference a value passed into one of the action's declared inputs (e.g., an input named NAME)?#
Options
Show answer
Inputs declared in a composite action's action.yml are referenced inside its steps with the inputs context: ${{ inputs.NAME }}. The calling workflow passes values through the with keyword, and those values become available in the action's own inputs context.
Inputs declared in the inputs section of a composite action's action.yml are referenced within steps using the inputs context: ${{ inputs.NAME }}. This is distinct from workflow-level contexts like env (environment variables), vars (repository variables), or steps (outputs of previously executed steps). The inputs context is scoped to the action itself and reflects the values supplied by the calling workflow via the with keyword.
Place the following steps in the order a developer would perform them when consuming a published GitHub Marketplace action for the first time in a new workflow. Order from earliest action to latest.#
Put these in order
Show answer
The correct order is: search the Marketplace for a suitable action, read its README for required inputs, add a uses: step in your workflow YAML, then provide required inputs with with:. Each step depends on the one before it — you need to find the action, understand its inputs, reference it, and then configure it.
The natural workflow is: first discover the action on the Marketplace, then read its documentation to understand what inputs it requires, then add a uses: step to the workflow YAML, and finally supply the required inputs with with:. Each step depends on the one before it — you cannot provide inputs you haven't read about, and you cannot add a uses: reference to an action you haven't found.
When referencing a Marketplace action with uses:, the reference can point to a branch, a major version tag, a specific release tag, or a commit SHA. Order these four reference styles from LEAST specific to MOST specific.#
Put these in order
Show answer
From least to most specific: branch name (@main), major version tag (@v4), specific release tag (@v4.1.0), and commit SHA. A branch changes on every push, a major tag moves with each release in the line, a specific tag is immutable, and a commit SHA pins one exact commit.
A branch name is the least specific because its content changes on every push. A major version tag like v4 is more specific because it tracks a release line but still moves as new minor/patch releases are published. A specific release tag like v4.1.0 is more specific still because it points to one immutable release. A full commit SHA is the most specific because it pins the exact, immutable commit in the repository's history.
When a workflow step that uses a Marketplace action runs, the following events occur. Place them in chronological order from first to last.#
Put these in order
Show answer
The chronological order is: fetch the action repository at the specified ref, read action.yml to determine the action type, execute the action's entrypoint (JS file, Docker image, or composite steps), then make any declared outputs available to subsequent steps. Each phase depends on the previous one completing.
First GitHub must fetch the action's repository content at the specified ref. Then it reads action.yml to determine how to run the action — whether it is a JavaScript action, a Docker action, or a composite action. Based on that, the action's entrypoint executes. Only after execution completes are any outputs the action declared available to later steps in the same job.
In GitHub Actions, what event trigger must a workflow expose in its on: block so that another workflow can call it as a reusable workflow?#
Show answer
workflow_call
A workflow becomes reusable when it lists workflow_call in its on: triggers. This special event allows other workflows to invoke it via the uses: key, optionally passing inputs and secrets.
When calling a reusable workflow from within a job, what job-level key do you use to specify the path (or org/repository reference) of the reusable workflow file?#
Show answer
uses
Inside a job, the uses: key references the reusable workflow — either by a relative path within the same repository (e.g. ./.github/workflows/deploy.yml) or by an org/repo/.github/workflows/file.yml@ref reference for a workflow in another repository.
GitHub Actions limits how deeply reusable workflows can be nested — i.e., a reusable workflow calling another reusable workflow. What is the maximum number of nesting levels allowed (counting the top-level caller as level 1)?#
Show answer
4
GitHub Actions enforces a maximum of 4 levels of workflow nesting. The top-level caller workflow counts as level 1, so it can chain up to three additional reusable workflows for a total of four. Beyond that, GitHub rejects the run.
Which of the following statements about GitHub Actions composite actions are true? (Select all that apply.)#
Options
Pick every one that applies.
Show answer
Composite actions can define inputs with default values in action.yml, and their steps can use uses to call other actions (including other composites). However, inner steps are NOT visible in the caller's steps context — only declared outputs are — and a composite step cannot invoke a reusable workflow via workflow_call, which is only valid at the job level.
Composite actions declare their interface — including inputs with optional default values — in action.yml under the inputs key, so that statement is true. A composite action step may use uses to call any action, including Docker actions and other composite actions, so that statement is true. The steps context inside a composite action is scoped to the steps within that composite; the calling workflow cannot see individual inner steps via steps.<composite-id>.outputs — it only sees the declared outputs — so the claim that steps executed inside a composite action are visible in the calling workflow's steps context under the composite action's id is false. Composite action steps cannot trigger a reusable workflow via workflow_call; that trigger is only available at the workflow job level, so the claim that a step inside a composite action can use workflow_call to invoke a reusable workflow is false.
In a GitHub Actions workflow step, the _____ key references a marketplace action. Its value follows the format owner/repo@ref, for example actions/checkout@v4. Unlike run:, which executes a shell command, this key tells the runner to fetch and execute the referenced action's code.#
Show answer
In a GitHub Actions workflow step, the uses key references a marketplace action. Its value follows the format owner/repo@ref, for example actions/checkout@v4. Unlike run:, which executes a shell command, this key tells the runner to fetch and execute the referenced action's code.
The uses key is the GitHub Actions workflow syntax for invoking an action — whether a marketplace action, a repo-relative local action, or a Docker image. The value is formatted as owner/repo@ref (or ./path for local actions, or docker://image:tag for container actions). The run: key, by contrast, executes inline shell commands and does not reference an action.
To pass input parameters to a marketplace action in a workflow step, you define them as key-value pairs under the _____ key. For example, actions/checkout accepts repository and ref through this key. Inputs declared with inputs: in the action's action.yml metadata file are received here by the consuming workflow.#
Show answer
To pass input parameters to a marketplace action in a workflow step, you define them as key-value pairs under the with key. For example, actions/checkout accepts repository and ref through this key. Inputs declared with inputs: in the action's action.yml metadata file are received here by the consuming workflow.
The with key is the workflow syntax for passing named inputs to an action. Each key under with maps to an input declared in the action's action.yml under inputs. This is distinct from env: (which sets environment variables) and secrets: (which is used at the job or workflow level, not for individual action inputs).
When a marketplace action publishes a result for downstream steps, it writes each value using echo "{name}={value}" >> "$GITHUB_OUTPUT". A later step in the same job accesses that value through the expression ${{ steps.<step-id>._____.<output-name> }}. The same key also appears in the action's action.yml under the outputs section that declares the output name and description.#
Show answer
When a marketplace action publishes a result for downstream steps, it writes each value using echo "{name}={value}" >> "$GITHUB_OUTPUT". A later step in the same job accesses that value through the expression ${{ steps.<step-id>.**outputs**.<output-name> }}. The same key also appears in the action's action.yml under the outputs section that declares the output name and description.
Action outputs are stored in the step context under the outputs sub-object. A step with id: my_step that sets an output named result makes that value available to subsequent steps in the same job via ${{ steps.my_step.outputs.result }}. The outputs key in action.yml declares the output's name and description for consumers, but the runtime value is always read from the steps.<id>.outputs context expression.
Which of the following are correct ways to access data within a composite action's steps?#
Options
Pick every one that applies.
Show answer
Composite action inputs are accessed via ${{ inputs.<name> }}, not github.event.inputs (which is for workflow dispatch events). Steps within a composite action reference sibling step outputs with ${{ steps.<id>.outputs.<name> }}, and the action's own outputs are declared in the outputs section of action.yml, mapped from step outputs.
Composite action inputs are accessed through the inputs context — ${{ inputs.my-input }} — so that is correct. Within a composite action, the steps context refers to steps defined inside that composite action, so ${{ steps.<step-id>.outputs.<name> }} correctly references a sibling step's output. The claim that github.event.inputs is used for composite action inputs is wrong because github.event.inputs is used for workflow_dispatch or repository_dispatch trigger inputs, not composite action inputs. Composite action outputs are declared in the outputs key of action.yml, with each output's value mapped from a step's output, making that correct.
Which of the following are valid limitations or constraints of composite actions in GitHub Actions?#
Options
Pick every one that applies.
Show answer
Composite actions cannot specify their own runs-on; they inherit the caller's runner. Step-level env variables are scoped to that step only and are not automatically shared with other steps. Composite actions can call other composite actions via uses and can use if conditionals on steps, so neither of those is a limitation.
Composite actions do not support a runs-on key; they always run on the runner provisioned by the calling workflow job, so this is a valid constraint. The claim that a composite action cannot call other composite actions is false — composite actions can call other composite actions (or any action) via uses. The statement about step-level env scoping is true: step-level env variables in GitHub Actions, including those in composite action steps, are scoped to that individual step; to share values across steps you must use the GITHUB_ENV file or define env at a higher level. The claim that a composite action cannot use if conditionals on its steps is false — if conditionals are fully supported on steps within composite actions.
In a GitHub Actions reusable workflow, what does specifying secrets: inherit in the calling job accomplish, and what is the key security trade-off?#
Show answer
All secrets available to the caller workflow are automatically passed to the called workflow without listing each one explicitly. This is convenient but means the called workflow gains access to every secret the caller can see, which broadens the attack surface — a compromised or untrusted called workflow could exfiltrate secrets the caller never intended to share. Prefer explicit secrets: mapping when calling workflows from untrusted sources.
secrets: inherit is a shorthand that forwards every secret accessible to the caller into the called workflow's context. The trade-off is that the called workflow receives the full secret surface rather than a least-privilege subset, so any compromise of the called workflow (or a supply-chain change to it) could expose secrets the caller did not need to share.
How are outputs defined and consumed when a caller workflow invokes a reusable workflow?#
Show answer
The called workflow declares outputs under on.workflow_call.outputs.<name>, mapping each to jobs.<job_id>.outputs.<name> where <job_id> is a job inside the called workflow. The called job must set those outputs using echo "<name>=<value>" >> $GITHUB_OUTPUT (or the Actions toolkit). The caller then reads them via needs.<calling_job_id>.outputs.<output_name>, where <calling_job_id> is the job in the caller workflow that has the reusable workflow invocation in its uses: key — not a job id from the called workflow.
The contract has two sides. On the called side, the reusable workflow registers output names at the workflow_call trigger level and binds each to a specific job's output within that called workflow; that job writes values via $GITHUB_OUTPUT. On the caller side, the needs context references jobs defined in the caller workflow. The job in the caller that invokes the reusable workflow (via uses:) becomes a dependency for downstream jobs, so those downstream jobs read the propagated outputs through needs.<calling_job_id>.outputs.<output_name> — using the caller's own job id, never a job id from the called workflow.
Can a GitHub Actions job that calls a reusable workflow with uses: also define its own steps: in the same job?#
Show answer
No — a job that invokes a reusable workflow via uses: is a call job and cannot also define steps:. The two keys are mutually exclusive within the same job. The call job may include with: (for inputs), secrets:, strategy: (including matrix), needs:, if:, environment:, and other job-level keys, but all actual work must live inside the called reusable workflow's own jobs and steps.
GitHub Actions enforces that uses: and steps: are mutually exclusive within a single job definition. A job is either a standard job that runs steps directly or a call job that delegates to a reusable workflow. To run additional steps alongside a reusable workflow, you must place them in separate jobs and orchestrate them with needs:.
In a GitHub Actions composite action (runs.using: composite), a step can reference the output of a prior step within the same composite action via steps.<step-id>.outputs.<name>, but that output is NOT automatically visible to the calling workflow as steps.<composite-step-id>.outputs.<name> unless the composite action explicitly declares it in the outputs mapping of its action.yml.#
Options
Show answer
True. Inside a composite action, steps can reference each other's outputs through the steps context, but those outputs are not automatically exposed to the calling workflow. The calling workflow can only access outputs that the composite action explicitly declares in the outputs mapping of its action.yml, mapping each internal step output to a named action-level output.
Within a composite action, the steps context is scoped to include steps defined inside that composite action, so a later step in the same composite can read an earlier step's outputs via steps.<step-id>.outputs.<name>. However, the calling workflow's steps context for the composite-action step (steps.<composite-step-id>) only exposes the outputs that the composite action's action.yml explicitly maps in its top-level outputs block (e.g., outputs: my-output: value: ${{ steps.inner-step-id.outputs.my-output }}). Without that mapping, the internal step output is invisible to the caller. The statement is TRUE.
When a run step inside a GitHub Actions composite action writes to $GITHUB_ENV or $GITHUB_PATH, those modifications persist for all subsequent steps in the calling job, including steps that execute after the composite action step completes.#
Options
Show answer
True
$GITHUB_ENV and $GITHUB_PATH are job-level shared files managed by the Actions runner. Any step that appends to them—including a run step inside a composite action—modifies the job's environment for every step that runs afterward, regardless of whether those later steps are inside the same composite action, in a different composite action, or directly in the calling job. There is no automatic scoping or rollback of these files when a composite action finishes. This is a well-documented behavior and a common gotcha when reusable composite actions unintentionally pollute the caller's environment. The statement is TRUE.
You are the platform engineering lead for a 300-engineer organization with 600 repositories on GitHub Enterprise Cloud. Teams currently use ad-hoc third-party actions from the GitHub Marketplace with no central policy — some pin to @v1, others use @main or commit SHAs they found in blog posts. Last quarter, a popular action (tj-actions/changed-files) was compromised and exfiltrated secrets from CI runners. Leadership has asked you to design a comprehensive strategy for governing the use of third-party marketplace actions across the organization. Your design must address: how teams select and vet actions, how versions are pinned and tracked, how provenance and trust are established, how the org enforces policy (allowlisting, runner isolation, etc.), and how ongoing maintenance (upgrading, auditing, responding to future incidents) is handled.#
Show answer
As platform lead, I would implement a five-layer governance strategy:
Pinning to immutable refs. All third-party actions must be pinned to a commit SHA, never a floating tag like @v1 or @main. Tags are mutable and can be force-pushed — exactly what happened with tj-actions/changed-files. We use Dependabot's github-actions ecosystem to automate SHA bumps: it opens PRs that update the SHA while preserving a # v1.2.3 comment for readability, and the PR includes a diff link so a reviewer can see what changed between the old and new SHAs.
Provenance & supply-chain verification. Before an action is added to the allowlist, it must pass a vetting checklist: (1) check the source repo's OpenSSF Scorecard for maintainer hygiene and branch protection; (2) verify the action publishes SLSA provenance attestation using slsa-verifier in our CI; (3) review the action's action.yml permissions field and the actual runs code for network calls, eval, or token exfiltration; (4) check for Sigstore signatures where available; (5) verify the maintainer's identity (verified org, published packages, established history). We reject actions that use run: bash with unpinned network fetches or that request broad permissions without justification.
Allowlisting & enforcement. At the GitHub Enterprise/org level, we enable 'Allow actions and reusable workflows by selected actions and reusable workflows,' restricting usage to only our allowlist. The allowlist lives in a central org-governance repo as a versioned YAML file, and an automation workflow syncs it to org settings via the GitHub API on every merge. As a second layer, we run actionlint plus custom OPA/Rego rules via Conftest as a required status check on every repo, catching non-allowlisted actions before they reach main.
Runner isolation & secret exposure. Third-party actions that need secrets run on ephemeral GitHub-hosted runners with minimal permissions: blocks — defaulting to permissions: {} (no tokens) and only adding what the action explicitly needs. Actions that don't need secrets get none. For high-risk actions, we prefer OIDC-based short-lived cloud credentials over long-lived PATs, and we consider container isolation (running the action in a Docker container with restricted network egress) for untrusted or high-privilege actions.
Ongoing audit & incident response. A weekly reporting workflow scans all repos' .github/workflows/ directories using the GitHub code search API, producing a dashboard of every action + version (SHA) used across the org. On a supply-chain incident, the playbook is: (a) block the action in the org allowlist immediately via an emergency PR + API sync; (b) use the inventory to identify all affected repos; (c) force-rotate any secrets that were accessible to the compromised action's job; (d) auto-create GitHub issues in each affected repo with remediation instructions; (e) provide a patched alternative action or a known-good SHA and update the allowlist.
This is a staff-level design exercise because it requires synthesizing supply-chain security, GitHub Actions platform features (enterprise policies, OIDC, Dependabot), and organizational governance into a single coherent strategy. The rubric covers the five pillars a platform team must address, and the sample answer demonstrates a concrete, implementable design for each.
Your organization has 50 product teams, each maintaining 5-15 repositories. Today, each repo has its own copy of a ~200-line CI workflow that installs dependencies, runs lint, tests, builds a container image, and deploys to a preview environment. You have been asked to design a sharing architecture that reduces duplication while preserving team autonomy. The design must address: when to use reusable workflows vs. composite actions vs. Docker container actions, how the sharing layer is structured (repo layout, versioning strategy), how inputs and secrets are passed, how teams override or extend the shared logic without forking it, and how breaking changes to the shared layer are rolled out. Justify your architectural decisions and identify the trade-offs.#
Show answer
I would design a layered sharing architecture with the following components:
Decision framework. Reusable workflows (workflow_call trigger) are for whole-pipeline orchestration — they define jobs, needs, matrix strategies, and runner selection, and they are the only construct that can be invoked as a top-level pipeline from another repo. Composite actions are for composable, reusable steps within a single job — they cannot define their own jobs, needs, or parallelism, so they are wrong for pipeline-level concerns. Docker container actions are for tooling that requires a consistent environment (e.g., a custom linter with many system dependencies that must run identically across Ubuntu, macOS, and Windows runners). The rule: if you need to control job topology, use a reusable workflow; if you need to compose steps inside a job, use a composite action; if you need environment encapsulation, use a Docker action.
Shared layer structure & versioning. A central org-ci-toolkit repo holds all reusable workflows in .github/workflows/ and all composite actions in actions/<name>/action.yml. Reusable workflows are consumed via uses: org-ci-toolkit/.github/workflows/[email protected]; composite actions via uses: org-ci-toolkit/actions/lint@v1. Versioning follows major-version release branches: v1 points to the latest 1.x commit, v2 to the latest 2.x. Teams pin to a major version (@v1) for automatic minor/patch updates, or to a specific tag (@v1.2.0) for maximum control. The major-version branch pattern means teams adopt breaking changes on their own schedule by bumping @v1 to @v2.
Secrets & inputs. Reusable workflows declare inputs and secrets in the workflow_call trigger definition. The calling workflow passes secrets explicitly: secrets: MY_DEPLOY_KEY or secrets: inherit to forward all. Inputs allow per-team customization (e.g., build-target, deploy-environment, node-version). Composite actions receive inputs via with: and cannot receive secrets the way workflows do — they access the job's secrets context through env: mappings, so the calling workflow passes the secret to the job and the composite action reads it from env.SECRET_NAME.
Extension without forking. Teams structure their repo's workflow as: (1) a job that calls the reusable workflow for the shared pipeline (uses: org-ci-toolkit/.github/workflows/ci.yml@v2), and (2) additional jobs with needs: [ci] that run team-specific steps (custom integration tests, Slack notifications, environment-specific deploys). For finer-grained insertion, the reusable workflow can expose hook inputs — e.g., a pre-test-steps composite action input that the team provides. The workflow runs that composite action before the test stage, so teams inject custom logic at a defined extension point without copying the shared code.
Breaking-change rollout. When a breaking change is needed, we cut a v2 major-version branch alongside v1. The v1 branch continues to receive critical bugfixes (backported) for a 90-day deprecation window. We add ::warning:: deprecation notices to v1 workflows: echo "::warning::This workflow version is deprecated; migrate to @v2 by 2024-09-01". We track adoption via a reporting workflow that scans all repos for @v1 vs @v2 usage and publishes a dashboard. After the window, v1 is archived, and a required status check flags any repo still referencing @v1.
This is a staff-level design exercise because it requires understanding the structural constraints of GitHub Actions' sharing primitives (reusable workflows vs composite actions vs Docker actions), designing a versioning and rollout strategy that preserves team autonomy, and articulating the extension and breaking-change patterns that real platform teams use at scale.
You are designing a 'golden path' internal action registry for a company that wants to move away from 100+ direct third-party marketplace action references toward a curated, internally-hosted set of composite actions. Each internal action wraps a vetted marketplace action (or replaces it entirely) and adds organization-specific hardening (fixed SHA pinning, restricted permissions:, input validation, audit logging). The design must address: the internal action catalog structure (how actions are named, discovered, documented), how the internal wrapper interacts with the underlying marketplace action (including what happens when the upstream action updates or is abandoned), how the org tracks which repos use which internal actions (and by extension which upstream marketplace actions), how teams request new actions or changes, and how deprecation and retirement of internal actions (and their upstreams) is communicated and enforced. Identify the key trade-offs of this approach versus allowing direct marketplace consumption.#
Show answer
I would design an internal 'golden path' action registry with the following architecture:
Internal wrapper & upstream lifecycle. Each internal action is a composite action in org-actions/<name>/action.yml that wraps a single marketplace action pinned to a specific commit SHA inside the wrapper's own action.yml. The SHA is managed by the platform team, not by 100+ individual repos. When the upstream releases a new version, the platform team evaluates it (runs the action's test suite, reviews the diff), bumps the SHA inside the wrapper, and releases a new version of the internal action (@v1.1.0). If the upstream is abandoned, the platform team forks it into org-actions/upstream-forks/<name>, pins the fork, and repoints the wrapper — consumers see no change. If the upstream is compromised, the platform team pins to a known-good SHA, swaps in an alternative action, or retires the internal action entirely. Because every repo references the wrapper (not the upstream directly), a single change to the wrapper propagates to all consumers on their next workflow run.
Hardening by the wrapper. Each internal action sets a restrictive permissions: block — e.g., contents: read only, no pull-requests: write or id-token: write unless the action's function explicitly requires it. The wrapper validates inputs before passing them to the upstream: for example, rejecting file paths containing .. traversal, enforcing allowed character sets, and clamping numeric inputs to safe ranges. No secrets: are passed to the upstream action unless the internal action's documentation explicitly calls for them, and when they are, they are scoped to the minimum (e.g., only the deploy key, not all secrets). The calling workflow's own permissions: block is the outer enforcement boundary — even if the internal action's action.yml requests broad scopes, the workflow-level permissions: {} (or a minimal set) constrains the actual token.
Usage tracking & inventory. A weekly reporting workflow scans all repos in the org using the GitHub code search API for uses: org-actions/ references, producing a dashboard that maps: repo → internal action → version → (transitively) upstream marketplace action + SHA. This serves as our CI software bill of materials. Dependabot also tracks the internal action versions, so when a team upgrades @v1 to @v1.1, we see it in the dashboard and know exactly which upstream SHA they are now running. This transitive visibility — knowing which upstream SHAs are in use across the org — is a key advantage that direct marketplace consumption cannot provide without equivalent scanning.
Catalog & request workflow. The org-actions repo has a README.md index table listing every action: name, one-line description, permissions required, upstream source URL, internal version, maintainer handle, and a link to detailed docs. Actions are also tagged with GitHub topics for discoverability. Teams request new actions by opening an issue from a template (action URL, use case, permissions needed, expected usage frequency). The platform team reviews the request, creates the wrapper, adds it to the org allowlist, and publishes it with docs. Changes to existing actions follow a PR review process — the platform team reviews the diff and runs CI before merging.
Trade-offs vs. direct marketplace consumption. (1) Reduced supply-chain risk — a single vetting point and single SHA management point — but (2) added indirection — teams read wrapper code, not the action they are actually running, which can obscure behavior. (3) Maintenance burden — the platform team must track every upstream's releases, CVEs, and breaking changes, which requires dedicated capacity. (4) Slower feature access — teams wait for the platform team to evaluate and bump the upstream, which can take days. (5) Bottleneck risk — if the platform team is under-staffed, the registry becomes a queue that blocks teams. Mitigations: a published SLA for new-action (48h) and upgrade (24h) requests, a self-service path for teams to contribute wrappers via PR with platform team review, and the ability for teams to pin a specific internal action version if they need to stay on a known-good state while the platform team processes an upstream bump.
This is a staff-level design exercise because it requires designing an internal platform abstraction (the wrapper registry) that mediates between external supply-chain risk and internal developer velocity, balancing governance with team autonomy, and articulating the concrete trade-offs of indirection versus direct consumption — a classic platform engineering trade-off at the staff level.
Related interview questions
Job market
See github-actions salaries and hiring demand from live job postings.
The other 142 questions
This page shows 25 and marks what you pick. That's as far as a page can go. A free account opens the other 142 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.
Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan