5.2.1. action.yml and Required Files
💡 First Principle: GitHub locates an action by finding action.yml (or action.yaml) at the referenced path — repository root for owner/repo@ref, or a subdirectory for owner/repo/path@ref — and everything else in the repository is just code that file references.
The full contract:
name: 'Deploy to Widget Cloud' # required — shown in logs and Marketplace
description: 'Deploys a build artifact' # required
author: 'my-org'
branding: # Marketplace listing only
icon: 'upload-cloud'
color: 'blue'
inputs:
environment:
description: 'Target environment'
required: true
timeout:
description: 'Seconds to wait'
required: false
default: '300'
deprecationMessage: 'Use wait-seconds instead'
outputs:
url:
description: 'Deployed URL'
# Docker/JS: value comes from the code writing $GITHUB_OUTPUT
# Composite: must map explicitly →
# value: ${{ steps.deploy.outputs.url }}
runs:
using: 'node20'
main: 'dist/index.js'
pre: 'dist/setup.js'
post: 'dist/cleanup.js'
post-if: 'success()'
Details that carry exam weight: name and description are required; input default values are strings; deprecationMessage warns consumers without breaking them; and composite actions must map each output to a step output with value:, unlike JavaScript and Docker actions where the code writes $GITHUB_OUTPUT directly. Repository layout conventionally includes a README.md (which becomes the Marketplace documentation), a LICENSE, the dist/ bundle for JavaScript actions, and — for Docker — the Dockerfile. One repository may hold multiple actions in subdirectories, but Marketplace publishing requires a single action with action.yml at the repository root (5.3.1).
The pre/post entry points deserve emphasis because only JavaScript actions have them: post runs even if the job failed (subject to post-if), making it the correct mechanism for cleanup that must always happen — revoking a temporary credential, stopping a tunnel, saving a cache.
⚠️ Exam Trap: A composite action's outputs need an explicit value: mapping in action.yml — writing to $GITHUB_OUTPUT inside a composite step is not enough to expose it to the caller. This asymmetry between composite and JS/Docker actions is a favorite distractor.
Reflection Question: Your action provisions a temporary cloud credential at the start of a job. Which runs: key guarantees revocation even when the job's later steps fail, and which action type is the only one offering it?