Chapter 17Lesson 05~255 minutes

Checkpoint Lab — Composite Actions, JavaScript Actions, Docker Actions, and Custom Automation

The checkpoint implements one local composite action as a documented string contract, deliberately sends an unsupported value so validation fails, preserves that run, repairs only the caller input, and produces a release plan that maps a human version to the exact commit SHA callers should pin.

Checkpoint labComposite contractValidation failureRelease SHAEvidence packet

Learning objectives

  • Implement a local composite action with documented enum/integer-like inputs and non-sensitive outputs.
  • Run a failing Revision A and preserve the action validation evidence before any repair.
  • Repair only the incompatible caller input and verify Revision B independently.
  • Record action metadata, runner context, action/workflow SHA and release-provenance mapping.
  • Produce a caller strategy that uses a full immutable commit SHA for external consumption.

1. Checkpoint scenario and safety boundary

Use a throwaway repository named gha-ch17-checkpoint. The lab creates no secret, package, release object, registry image, environment, cloud resource or self-hosted runner. It uses one local composite action and one SHA-pinned checkout action on ubuntu-24.04.

The “typed” contract is intentionally implemented as documented string semantics because custom-action metadata does not support workflow_call-style type declarations. mode is an enum string and repeat is an integer-like string validated before use.

2. Preflight and predictions

  • Confirm the repository is disposable and contains no proprietary source.
  • Confirm Actions is enabled and ubuntu-24.04 is available.
  • Record the exact commit containing action.yml and the workflow before each run.
  • Predict Revision A: checkout succeeds, local action starts, mode validation exits 64, output verification never runs.
  • Predict Revision B: the same action implementation accepts strict, returns two outputs and produces no external side effect.

3. The action contract

# .github/actions/ch17-contract/action.yml
name: Chapter 17 checkpoint contract
description: Validate caller strings and return a non-sensitive summary
inputs:
  mode:
    description: "Enum string: strict or relaxed"
    required: true
  repeat:
    description: "Integer string from 1 through 3"
    required: false
    default: "1"
outputs:
  summary:
    description: Normalized summary
    value: ${{ steps.validate.outputs.summary }}
  contract_kind:
    description: Fixed classification
    value: ${{ steps.validate.outputs.contract_kind }}
runs:
  using: composite
  steps:
    - id: validate
      shell: bash
      env:
        MODE: ${{ inputs.mode }}
        REPEAT: ${{ inputs.repeat }}
      run: |
        set -euo pipefail
        case "$MODE" in
          strict|relaxed) ;;
          *) echo "unsupported mode" >&2; exit 64 ;;
        esac
        [[ "$REPEAT" =~ ^[1-3]$ ]] || {
          echo "repeat must be 1, 2, or 3" >&2
          exit 65
        }
        printf 'summary=mode-%s-repeat-%s
' "$MODE" "$REPEAT" >> "$GITHUB_OUTPUT"
        printf 'contract_kind=validated-string-contract
' >> "$GITHUB_OUTPUT"

4. Workflow Revision A — preserve a real validation failure

# .github/workflows/ch17-checkpoint.yml — Revision A
name: chapter17-checkpoint
on: workflow_dispatch
permissions: {}

jobs:
  validate:
    runs-on: ubuntu-24.04
    steps:
      - name: Record bounded run evidence
        shell: bash
        run: |
          echo "repository=$GITHUB_REPOSITORY"
          echo "sha=$GITHUB_SHA"
          echo "run=$GITHUB_RUN_ID attempt=$GITHUB_RUN_ATTEMPT"
          echo "runner_os=$RUNNER_OS arch=$RUNNER_ARCH"

      - name: Checkout exact caller revision
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - id: contract
        name: Execute contract
        uses: ./.github/actions/ch17-contract
        with:
          mode: unsupported
          repeat: '2'

      - name: Verify outputs
        shell: bash
        env:
          SUMMARY: ${{ steps.contract.outputs.summary }}
        run: test "$SUMMARY" = "mode-strict-repeat-2"

Commit Revision A and dispatch it. Preserve the run ID, attempt and source SHA. The expected first failure is the Execute contract step with exit 64 and the generic unsupported mode message. The verification step is skipped because the prior step failed.

5. Interpret before repairing

Observed state Expected Revision A evidence Meaning
Workflow/run manual run exists at recorded SHA workflow syntax and trigger were valid
Checkout success at pinned v7.0.1 action SHA repository-local action files are present
Action metadata runs.using: composite correct execution model selected
Validation exit 64, unsupported mode caller value violates documented enum contract
Outputs not consumed failed action did not produce a valid contract result
External state none failure is bounded to the run/workspace

Do not modify action code, runner, permissions or checkout when this evidence matches the prediction. Those layers are healthy.

6. Workflow Revision B — least destructive repair

# Change exactly one caller value.
with:
  mode: strict
  repeat: '2'

Commit only that change and dispatch a new run. Expected output is mode-strict-repeat-2. Record the new source SHA and compare it with Revision A. The action metadata can be byte-for-byte identical across the two commits if only the workflow input changed.

7. Build an immutable release strategy without publishing anything

After Revision B succeeds, identify the reviewed commit that contains the action implementation you would release. Record a human version such as v1.0.0 and map it to the exact 40-character commit SHA. In a real action repository, you may publish that tag/release, but mandatory completion stops at the release plan.

Release record
name: v1.0.0
action path: .github/actions/ch17-contract
source commit: <REVIEWED_40_CHARACTER_SHA>
runtime: composite
required permissions: none
supported runner baseline: caller must provide Bash; lab verified ubuntu-24.04
inputs: mode=strict|relaxed; repeat=1|2|3
outputs: summary; contract_kind
external side effects: none
# Production caller pattern after release — pin the exact recorded commit.
- uses: OWNER/REPO/.github/actions/ch17-contract@REVIEWED_40_CHARACTER_SHA
  with:
    mode: strict
    repeat: '2'

A release tag can point reviewers to documentation, but callers that require immutable supply-chain identity should execute the full SHA.

8. Required evidence packet

Evidence field Record
Event/revision workflow_dispatch; Revision A and B source SHAs
Run identity run ID and attempt for broken and repaired runs
Action identity local path plus exact source commit containing action.yml
Metadata input/output names and runs.using: composite
Runtime/tooling ubuntu-24.04; Bash; checkout v7.0.1 at pinned SHA
Permissions permissions: {}; no secret or token capability required
First failure Execute contract; exit 64; unsupported mode
Repair caller changes only mode unsupported → strict
Outputs mode-strict-repeat-2 and validated-string-contract
Release provenance v1.0.0 → reviewed 40-character action commit SHA
External state none
Limitations composite relies on documented Bash-compatible runner; JS/Docker alternatives were design comparisons only

9. Verification checklist

  • ZIP/course example contains no real token, key or registry credential.
  • Revision A exists as an independent failed run and was not overwritten.
  • The first failure is action input validation, not checkout/path/syntax.
  • Revision B changes only the caller mode value and succeeds.
  • Output values are non-sensitive and deterministic.
  • No write-all, secret dump, pull_request_target, Docker socket or privileged execution is used.
  • Release plan records both human version and full immutable source SHA.
  • External callers are instructed to pin the full SHA, not @main or only @v1.

10. Cleanup and rollback

There is no external infrastructure to roll back. Preserve non-sensitive screenshots/run IDs if desired, then delete the throwaway repository. If you optionally created a real release/tag for practice, remove only the exact disposable release/tag that you created after recording its SHA mapping.

11. What Chapter 17 adds to the operating model

You can now treat custom actions as versioned executable dependencies with an explicit step-level contract: metadata defines the surface, the execution model defines portability/runtime assumptions, the caller owns permissions, the implementation validates untrusted input, outputs remain small and non-sensitive, and release provenance binds human versions to immutable code. Chapter 18 expands reuse from individual actions and reusable workflows into Workflow Templates, Organization Standards, YAML Anchors, and Reuse Architecture.

Knowledge check

Why does the checkpoint call its inputs typed/documented rather than adding type: string to action.yml?

What is the causal repair for Revision A?

Why keep the failed Revision A run after Revision B succeeds?

What should an external caller pin after v1.0.0 is released?

What does Chapter 18 add beyond Chapter 17?

Next chapter concept

Scale reuse into standards

Chapter 18 examines workflow templates, organization standards, YAML anchors and how to choose between copied and referenced automation.

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked on 2026-09-10 for GitHub.com. New JavaScript actions should target runs.using: node24. GitHub moved JavaScript actions to Node 24 by default in 2026; Node 20 is in its final deprecation window and is scheduled for removal from Actions runners on 2026-09-23. Current @actions/core source reports version 3.0.1 and is ESM-only. Action metadata inputs do not provide reusable-workflow-style typed schemas; document the expected type/enum and validate the string value in the action. Composite actions access declared values through the inputs context and should use GITHUB_ACTION_PATH for action-relative scripts. JavaScript actions can define pre/main/post; Docker actions can define pre-entrypoint/entrypoint/post-entrypoint, with pre/main/post Docker phases running in distinct containers. Docker container actions run only on Linux runners; self-hosted runners also need Docker. Production references to external actions should use a reviewed full commit SHA and record the release/tag that maps to it. The mandatory lab uses actions/checkout v7.0.1 pinned to 3d3c42e5aac5ba805825da76410c181273ba90b1; every other executable action is repository-local. The checkpoint intentionally does not publish a release or external action. It creates the exact release-provenance record a publisher would need, while keeping mandatory learning free, local to a disposable repository and side-effect bounded.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.