Copyright (c) 2026 MindMesh Academy. All rights reserved. This content is proprietary and may not be reproduced or distributed without permission.

5.2.2. Workflow Commands Inside Actions

💡 First Principle: An action talks to the runner through the same channels a workflow step does — environment files and workflow commands (2.2.2) — but the @actions/core toolkit wraps them in functions, which is why toolkit calls and echo "::..." lines are two spellings of one mechanism.

The mapping is worth knowing in both directions, because exam snippets appear in either form:

Toolkit callUnderlying mechanismEffect
core.setOutput(name, value)writes $GITHUB_OUTPUTExposes a step output
core.exportVariable(name, value)writes $GITHUB_ENVEnv var for later steps
core.addPath(dir)writes $GITHUB_PATHPrepends to PATH
core.setSecret(value)::add-mask::Masks the value in logs
core.setFailed(msg)::error:: + exit 1Fails the step with a reason
core.error/warning/notice(msg)::error::/::warning::/::notice::Annotations, no failure
core.debug(msg)::debug::Only with ACTIONS_STEP_DEBUG
core.startGroup/endGroup(name)::group::/::endgroup::Collapsible log section
core.getInput(name)reads INPUT_<NAME>Reads a declared input
core.saveState/getState$GITHUB_STATEPasses data from main to post

Two of these are action-specific. core.setSecret() lets an action mask a value it computed — a token it just minted, for example — so the value is redacted for the rest of the run; this is the correct behavior for any action that handles credentials. And saveState/getState is the only channel between an action's main and post entry points, which is how a cleanup step knows what to clean up.

Composite actions use the shell forms directly, since there's no JavaScript process:

runs:
  using: composite
  steps:
    - shell: bash
      run: |
        echo "::group::Provisioning"
        echo "token=$(./mint.sh)" >> "$GITHUB_OUTPUT"
        echo "::endgroup::"

⚠️ Exam Trap: The deprecated ::set-output:: and ::save-state:: commands were disabled in 2023 — current actions write to $GITHUB_OUTPUT and $GITHUB_STATE. Any snippet using echo "::set-output name=x::y" is wrong on today's exam, no matter how many blog posts still show it.

Reflection Question: An action mints a short-lived token and prints a status line containing it. core.setSecret() would have masked it. Explain what masking does and does not protect against, referencing 2.2.6.

See how it connects
Alvin Varughese
Written byAlvin Varughese
Founder18 professional certifications