Automation: scheduled, dispatched, and cross-workflow jobs
What this domain tests
Automation goes beyond test-on-push. Expect questions on non-code triggers (schedule, workflow_dispatch, repository_dispatch, workflow_run), passing inputs safely, moving data between jobs with artifacts and cache, and the token rules that decide whether one workflow can start another.
Inputs and dispatch
workflow_dispatchsupports at most 10 inputs typed asstring,choice,boolean, orenvironment, each withrequiredanddefault.- Read them through the
inputscontext to preserve types:inputs.dry_runis a real boolean, whilegithub.event.inputs.dry_runis always a string. repository_dispatchis fired through the REST API with atypeand arbitraryclient_payload; the calling token needs write access to the repository.workflow_runtriggers after another workflow completes and runs in the base repository context, with secrets - treat it as privileged.
Data between jobs and runs
- Artifacts (
actions/upload-artifact@v4/download-artifact@v4) are immutable and require unique names per job; v3 is retired. - Cache matching tries the exact
keyfirst, then prefixrestore-keys. Caches are branch-scoped: default-branch caches are visible elsewhere, but a feature branch's cache is not. - Caches are evicted after roughly seven days unused; artifacts follow the
retention-dayssetting instead.
Token behaviour rule
Events created with the automatic GITHUB_TOKEN do not start new workflow runs, except for workflow_dispatch and repository_dispatch. If a job must chain into another workflow, use a GitHub App token or a PAT.
Worked example
A nightly housekeeping job with a manual dry-run option:
on:
schedule: [{cron: '0 3 * * *'}]
workflow_dispatch:
inputs:
dry_run: {type: boolean, default: true}
permissions:
issues: write
contents: read
jobs:
stale:
runs-on: ubuntu-latest
if: github.repository == 'acme/app'
steps:
- uses: actions/github-script@v7
with:
script: |
if (inputs.dry_run) { core.info('dry run'); return }
await github.rest.issues.listForRepo({owner: context.repo.owner, repo: context.repo.repo, state: 'open'})
Reasoning: inputs.dry_run gives a boolean instead of the truthy string 'false'; the if guard stops forks of the repo from running the schedule; issues: write is the only elevated scope granted.
Traps that cost marks
- Scheduled workflows are disabled after about 60 days of repository inactivity.
workflow_runcombined with checking out untrusted PR code hands an attacker secrets and a write token.- Array and object
client_payloadvalues arrive as JSON strings and needfromJSON. - Downloading artifacts requires the same run (or explicit
run-idwith a token); jobs on different runners share nothing else.