Summary
When a workflow declares both workflow_call and another trigger (e.g. workflow_dispatch), actionlint types the secrets context exclusively from on.workflow_call.secrets — and applies that closed type to every job, including jobs that can only ever run on the non-workflow_call path.
On those paths the workflow is not being called, so secrets is the ordinary repository/organization secrets context and any secret is legitimately available. actionlint reports a valid reference as an error.
Reproduction
.github/workflows/repro.yml:
name: Repro
on:
workflow_call:
secrets:
declared_secret:
required: false
workflow_dispatch:
jobs:
# Only ever runs on the workflow_dispatch path, where the workflow is NOT
# being called and `secrets` is the normal repo/org secrets context.
self-test:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- run: echo "${{ secrets.MY_REPO_SECRET }}"
Actual
.github/workflows/repro.yml:16:24: property "my_repo_secret" is not defined in object type {actions_runner_debug: string; actions_step_debug: string; declared_secret: string; github_token: string} [expression]
|
16 | - run: echo "${{ secrets.MY_REPO_SECRET }}"
| ^~~~~~~~~~~~~~~~~~~~~~
Exit code 1.
Expected
No error. MY_REPO_SECRET is a repository secret, and the job that reads it runs only via workflow_dispatch, where the workflow_call secrets declaration does not constrain the secrets context.
Version
1.7.12
installed from Homebrew
Why this looks different from #141 / #148
Both existing issues are about what is visible inside a called reusable workflow:
In both, the workflow is being called, and the objection raised there applies: actionlint cannot know which org-level secrets exist, so it cannot verify the name.
This report is narrower and doesn't require knowing any secret names. The issue is that the closed type is applied to a trigger path where no workflow_call restriction exists at all. actionlint already permits arbitrary secrets.* in workflows that have no workflow_call trigger; the same workflow gains a closed secrets type purely by also being callable, even for jobs gated to another event.
Possible directions
Roughly in order of precision:
- When
on contains any trigger besides workflow_call, don't treat the workflow_call.secrets type as exhaustive — fall back to the open secrets type used for non-reusable workflows. Loses call-path checking, but matches the behaviour of a plain workflow.
- Narrow by job: if a job's
if: restricts it to non-workflow_call events, use the open type for that job and keep the strict type elsewhere. More precise, but requires reasoning about if: expressions.
- Keep the current behaviour but make it suppressible without a repo-wide config change. Today the only escapes are
-ignore and paths: in .github/actionlint.yaml, both of which disable the rule for the whole file — including genuine undefined-secret references in the reusable half, which is exactly where the check is most valuable. There appears to be no inline directive equivalent to # shellcheck disable=; if one exists I'd be glad to be corrected.
Option 1 alone would resolve this, and is the same trade-off already made for workflows without workflow_call.
Workaround (for anyone hitting this)
Split the dispatch-only job into its own workflow file, leaving the reusable as pure workflow_call. Both halves then type correctly and no suppression is needed.
Summary
When a workflow declares both
workflow_calland another trigger (e.g.workflow_dispatch), actionlint types thesecretscontext exclusively fromon.workflow_call.secrets— and applies that closed type to every job, including jobs that can only ever run on the non-workflow_callpath.On those paths the workflow is not being called, so
secretsis the ordinary repository/organization secrets context and any secret is legitimately available. actionlint reports a valid reference as an error.Reproduction
.github/workflows/repro.yml:Actual
Exit code 1.
Expected
No error.
MY_REPO_SECRETis a repository secret, and the job that reads it runs only viaworkflow_dispatch, where theworkflow_callsecrets declaration does not constrain thesecretscontext.Version
Why this looks different from #141 / #148
Both existing issues are about what is visible inside a called reusable workflow:
secrets: inheritwith declared secrets.In both, the workflow is being called, and the objection raised there applies: actionlint cannot know which org-level secrets exist, so it cannot verify the name.
This report is narrower and doesn't require knowing any secret names. The issue is that the closed type is applied to a trigger path where no
workflow_callrestriction exists at all. actionlint already permits arbitrarysecrets.*in workflows that have noworkflow_calltrigger; the same workflow gains a closedsecretstype purely by also being callable, even for jobs gated to another event.Possible directions
Roughly in order of precision:
oncontains any trigger besidesworkflow_call, don't treat theworkflow_call.secretstype as exhaustive — fall back to the opensecretstype used for non-reusable workflows. Loses call-path checking, but matches the behaviour of a plain workflow.if:restricts it to non-workflow_callevents, use the open type for that job and keep the strict type elsewhere. More precise, but requires reasoning aboutif:expressions.-ignoreandpaths:in.github/actionlint.yaml, both of which disable the rule for the whole file — including genuine undefined-secret references in the reusable half, which is exactly where the check is most valuable. There appears to be no inline directive equivalent to# shellcheck disable=; if one exists I'd be glad to be corrected.Option 1 alone would resolve this, and is the same trade-off already made for workflows without
workflow_call.Workaround (for anyone hitting this)
Split the dispatch-only job into its own workflow file, leaving the reusable as pure
workflow_call. Both halves then type correctly and no suppression is needed.