Skip to content

fix: local workflow recursion, recursive exclusions, and remote composite path handling#81

Merged
joshjohanning merged 7 commits into
joshjohanning:mainfrom
Wuodan:upstream-PR/01-fix-local-reusable-workflow-recursion-and-exclusions
May 5, 2026
Merged

fix: local workflow recursion, recursive exclusions, and remote composite path handling#81
joshjohanning merged 7 commits into
joshjohanning:mainfrom
Wuodan:upstream-PR/01-fix-local-reusable-workflow-recursion-and-exclusions

Conversation

@Wuodan

@Wuodan Wuodan commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR contains 3 functional fixes and 1 parser hardening change:

  • fix local reusable workflow handling vs local action handling
  • fix exclude-workflows so it is respected during traversal
  • fix remote composite local ref resolution
  • harden uses parsing against malformed input

Fixes

1. Handle local reusable workflows separately from local actions

  • detect local reusable workflow refs explicitly
  • recurse into nested local reusable workflows
  • resolve local reusable workflow paths with workflow-specific rules
  • stop recursive local workflow cycles

2. Respect exclude-workflows throughout traversal

  • apply exclusions during nested local reusable workflow traversal
  • apply exclusions during remote reusable workflow traversal
  • let exclusions win even when workflows is explicitly set
  • skip excluded reusable workflow refs before they are added to the action set

3. Resolve remote composite local refs from repo root

  • 12c5e55 added remote composite recursion
  • that also started resolving nested ./... refs relative to the composite action directory
  • ./... is the local-path form in GitHub Actions
  • remote content is addressed as {owner}/{repo}/...@ref
  • so action-directory-relative remote ./... expansion cannot work under the documented syntax
  • resolve those refs from repo root instead

4. Harden action reference parsing

  • replace regex-based parsing with deterministic string parsing
  • keep support for owner/repo@ref
  • keep support for owner/repo/path@ref
  • keep support for multi-segment action paths
  • add parser coverage for malformed input

Validation

I validated these changes using two companion repos for recursive test coverage:

  • Wuodan/ensure-immutable-actions-test
    • workflow-under-test repo
    • runs this action and compares outputs to expected JSON
  • Wuodan/ensure-immutable-actions-test-fixtures
    • fixture repo
    • contains remote reusable workflows, nested reusable workflows, local actions, and remote composite fixtures used by the test repo

Reference runs:

- Handle local action paths and local reusable workflow paths separately, matching GitHub semantics.
- Recurse into local reusable workflows during extraction.
- Apply exclude-workflows inside that recursion.
- Stop recursion on local reusable workflow cycles.
- Reject workflow paths that escape the workspace.
Wuodan added 3 commits April 28, 2026 00:12
Intent:
- make exclude-workflows mean the same thing everywhere
- let exclusions win even when workflows is set
- skip excluded reusable workflows during recursive traversal

Technical:
- apply exclude-workflows after top-level workflow selection in both modes
- drop excluded reusable workflow refs before adding them to the action set
- propagate excludeWorkflowPatterns through remote recursion
- add tests for workflows+exclude-workflows overlap and excluded remote reusable traversal
Background:
- 12c5e55 added remote composite recursion
- it also started resolving nested `./...` refs relative to the composite action directory

Why:
- `./...` is the local-path form in GitHub Actions
- a remote ref only exists in `{owner}/{repo}/...@ref` form
- so treating `./...` inside a remote composite action as action-directory-relative remote content cannot work under the documented syntax
- for this scanner, such refs must be interpreted as repo-root paths if we expand them at all

Technical:
- stop joining remote composite local refs with `action.actionPath`
- normalize `./...` directly from repo root
- update regression coverage to use a repo-root `.github/actions/...` lookup
Replace the regex-based action reference parser with deterministic string parsing to avoid polynomial backtracking on malformed uses strings.

Keep support for owner/repo, single-segment path, and multi-segment path references, and add parser coverage for invalid and multi-segment path cases.
@Wuodan Wuodan force-pushed the upstream-PR/01-fix-local-reusable-workflow-recursion-and-exclusions branch from 9764080 to b7eddf8 Compare April 27, 2026 22:12
@Wuodan

Wuodan commented Apr 27, 2026

Copy link
Copy Markdown
Contributor Author

I would happily make you owner of the test and fixture repos if you want - I would then fork them.
Just beware that keeping immutable refs in sync there takes some getting used to.

Just tell if you want that.

Example of complexity: Change commit A in fixtures repo

  • follow up commit B in fixture repo to update all immutable references to commit A (fixture to fixture)
  • always update all immutable references for the sake of your sanity (so expected action output in test repo is manageable per branch fixtgure branch and not mixed per test)
  • use placeholders in test repo for SHA and branch references to fixture repos

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves recursive workflow/action traversal for the Ensure Immutable Actions GitHub Action by correctly distinguishing local reusable workflows from local actions, honoring exclude-workflows across traversal/expansion, and tightening uses: reference parsing.

Changes:

  • Reworked uses: parsing to deterministic string parsing (no regex backtracking) and added malformed-input test coverage.
  • Added local reusable workflow detection/recursion with cycle prevention, and threaded exclude-workflows through traversal.
  • Fixed remote composite ./... reference handling to resolve from repo root, and expanded tests/docs accordingly.
Show a summary per file
File Description
src/index.js Core logic changes: hardened parsing, local reusable workflow recursion, exclude-workflows propagation, and remote composite ./... handling.
tests/index.test.js Added/updated tests for multi-segment action paths, malformed uses parsing, local reusable workflow recursion/exclusion, and remote traversal behaviors.
README.md Updated exclude-workflows input docs to reflect new behavior.
badges/coverage.svg Updated coverage badge output.

Copilot's findings

  • Files reviewed: 3/4 changed files
  • Comments generated: 5

Comment thread src/index.js Outdated
Comment thread src/index.js Outdated
Comment thread __tests__/index.test.js
Comment thread src/index.js
Comment thread README.md
@joshjohanning

joshjohanning commented May 5, 2026

Copy link
Copy Markdown
Owner

Thanks for the thorough PR @Wuodan! I pushed a small fixup commit (44d5098) to address the review findings. Here's what I changed:

  1. Removed unused baseDir parameter from extractActionsFromLocalReusableWorkflow and its call site in addParsedAction — this would have failed ESLint's no-unused-vars
  2. Moved exclusion check before file I/O — previously, an excluded local reusable workflow that was also missing/unreadable would surface as an "unsupported" finding instead of being silently skipped. The exclusion check now runs first.
  3. Handled normalize('./')edge case in expandRemoteCompositeActionpath.posix.normalize('./')returns '.', which would produce a malformed owner/repo/.@ref reference. Added a special case so uses: ./ correctly maps to owner/repo@ref.
  4. Updated action.yml description for exclude-workflows — it still said "Only applies when workflows are not specified" but the behavior changed to always apply.
  5. Removed extra ignored test argumentresolveLocalReusableWorkflowPath only accepts 2 params, the test was passing 3.
  6. Bumped version to 2.5.3.

One thing I want to flag for discussion: the exclude-workflows matching for remote reusable workflows is basename-only (e.g., excluding ci.yml would also skip some-org/some-repo/.github/workflows/ci.yml@v1). Common workflow names like ci.yml or deploy.yml could unintentionally suppress traversal into third-party reusable workflows, hiding mutable actions. Worth considering whether this should be documented more explicitly or scoped differently — curious what you think.

- Remove unused baseDir parameter from extractActionsFromLocalReusableWorkflow
- Move exclusion check before file I/O to prevent excluded+missing files
  from surfacing as unsupported findings
- Handle normalize('./') edge case in remote composite expansion that
  would produce malformed owner/repo/.@ref references
- Update action.yml exclude-workflows description to match new behavior
- Remove extra ignored argument in resolveLocalReusableWorkflowPath test
- Bump version to 2.5.3
@joshjohanning joshjohanning force-pushed the upstream-PR/01-fix-local-reusable-workflow-recursion-and-exclusions branch from 1b046d6 to 44d5098 Compare May 5, 2026 16:13
@joshjohanning joshjohanning requested a review from Copilot May 5, 2026 16:14
@joshjohanning joshjohanning changed the title Fix local workflow recursion, recursive exclusions, and remote composite path handling fix: local workflow recursion, recursive exclusions, and remote composite path handling May 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 5/7 changed files
  • Comments generated: 4

Comment thread src/index.js
Comment thread src/index.js
Comment thread __tests__/index.test.js Outdated
Comment thread __tests__/index.test.js
- Apply the same resolvedPath === '.' guard to expandRemoteReusableWorkflow
  (both job-level and step-level ./  refs) to prevent malformed
  owner/repo/.@ref references
- Remove extra ignored argument in path traversal test
@joshjohanning joshjohanning changed the title fix: local workflow recursion, recursive exclusions, and remote composite path handling fix: local workflow recursion, recursive exclusions, and remote composite path handling May 5, 2026
@joshjohanning joshjohanning merged commit 6cc152d into joshjohanning:main May 5, 2026
1 check passed
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

📦 Draft Release Created

A draft release v2.5.3 has been created for this PR.

🔗 View Draft Release

Next Steps

  • Review the release notes
  • Publish the release to make it permanent

This is an automated reminder from the publish-github-action workflow.

@Wuodan Wuodan deleted the upstream-PR/01-fix-local-reusable-workflow-recursion-and-exclusions branch May 10, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants