Skip to content

False positive: workflow_call secrets type is applied to jobs on other triggers (e.g. workflow_dispatch) #701

Description

@impuls42

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:

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions