{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreifymg3sfu7qk2ocbtswq27z56n4m7xaectp5yi5ay4tpp7u6qdf7a",
"uri": "at://did:plc:wszrgoqdwy3i2dfeub2mt3wf/app.bsky.feed.post/3mf4yknh6nhz2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreiep5dsa2ip64fanl23ckaegh7pnbzyisteupyynsmg2humj63gwc4"
},
"mimeType": "image/png",
"size": 13001
},
"description": "A gotcha with how `required: true` allows an empty string as valid input.",
"path": "/posts/2026/02/18/github-actions-required/",
"publishedAt": "2026-02-18T11:28:31.000Z",
"site": "https://www.jvt.me",
"tags": [
"blogumentation",
"github",
"github-actions",
"Renovate",
"actionlint"
],
"textContent": "Earlier today on Renovate we noticed that our documentation site had not been deploying for a couple of days.\n\nThe root cause was down to a quirk of GitHub Actions, where a `required` property isn't actually always enforced to be required 😅\n\nTo explain this, let's look at the following composite action:\n\n\n # .github/actions/setup-node/action.yml\n name: 'Setup Node and install dependencies'\n description: 'Setup Node and install dependencies using cache'\n inputs:\n node-version:\n description: 'Node version'\n required: true\n os:\n description: 'Composite actions do not support `runner.os`, so it must be passed in as an input'\n required: true\n save-cache:\n description: 'Save cache when needed'\n required: false\n default: 'false'\n runs:\n using: 'composite'\n steps:\n - name: Calculate `CACHE_KEY`\n shell: bash\n run: |\n echo 'CACHE_KEY=node_modules-${{\n inputs.os\n }}-${{\n inputs.node-version\n }}-${{\n hashFiles('pnpm-lock.yaml', 'package.json')\n }}' >> \"$GITHUB_ENV\"\n\n # ...\n\n\nThis is then used as part of our build:\n\n\n # .github/workflows/build.yml\n build-deploy-docs-site:\n needs: release\n\n permissions: # ...\n\n environment: # ...\n\n runs-on: ubuntu-latest\n steps:\n - name: Checkout code\n uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n with:\n show-progress: false\n\n - name: Setup Node.js\n uses: ./.github/actions/setup-node\n with:\n node-version: ${{ needs.setup-build.outputs.node-version }}\n os: ${{ runner.os }}\n\n\nCan you spot what's wrong here?\n\nYou may notice this line:\n\n\n node-version: ${{ needs.setup-build.outputs.node-version }}\n\n\nWe're referencing a job, but that wasn't found in our `needs` definition.\n\nIn this case, GitHub Actions is therefore evaluating this statement as the empty string, resolving as `node-version: \"\"`.\n\nBecause there is _technically_ something passed to the `node-version` argument, `required: true` isn't flagging any issues.\n\nIn my opinion, `required: true` should error if it's receiving empty input, but maybe this is instead a slightly painful gotcha, but one I'm now going to going to remember going forwards.\n\nWe've since added a check in our composite action to fail the build if either `required: true` options are unset.\n\nIt looks like actionlint would have caught this too - so that's on my TODO list to add to our CI pipelines now, too!",
"title": "GitHub Actions' required properties aren't always required",
"updatedAt": "2026-02-18T11:28:31.000Z"
}