Skip to content

Fix case-insensitive dedup of action downloads#4326

Draft
stefanpenner wants to merge 3 commits intoactions:mainfrom
stefanpenner:case-insensitive-action-dedup
Draft

Fix case-insensitive dedup of action downloads#4326
stefanpenner wants to merge 3 commits intoactions:mainfrom
stefanpenner:case-insensitive-action-dedup

Conversation

@stefanpenner
Copy link
Copy Markdown
Contributor

@stefanpenner stefanpenner commented Apr 8, 2026

Summary

  • Fixes action deduplication to use case-insensitive comparison for owner/repo names, so actions/checkout and actIONS/checkout are correctly treated as the same action and downloaded only once
  • Uses a custom ActionLookupKeyComparer that treats the owner/repo portion of lookup keys case-insensitively while keeping the ref portion case-sensitive (git refs are case-sensitive)
  • Normalizes action reference names after dedup lookup (NormalizeRepositoryReference) so that directory paths are consistent on case-sensitive filesystems (Linux)
  • Applies across both the batch resolution path and the legacy code path

Fixes #3731

Changes

  1. ActionLookupKeyComparer — custom IEqualityComparer<string> for "{owner/repo}@{ref}" keys: name portion is case-insensitive, ref portion is case-sensitive
  2. Dedup collections — batch cache dictionary, pending-key set, GroupBy in GetDownloadInfoAsync, and returned download info dictionary all use the new comparer
  3. NormalizeRepositoryReference — after dedup lookup, updates each action step's repositoryReference.Name to match the canonical downloadInfo.NameWithOwner, preventing directory mismatches on Linux
  4. Regression testPrepareActions_DeduplicatesCaseInsensitiveActionReferences verifies three differently-cased references resolve once, download succeeds, and references are normalized

Test plan

  • New test PrepareActions_DeduplicatesCaseInsensitiveActionReferences passes
  • All 36 existing ActionManagerL0 tests pass
  • Verify in a live workflow with mixed-case action references (e.g. actions/checkout vs actIONS/checkout) that the action is downloaded only once

🤖 Generated with Claude Code

The deduplication logic introduced in actions#4296 used StringComparer.Ordinal,
so action references differing only by owner/repo casing (e.g.
actions/checkout vs actIONS/checkout) were treated as distinct and
downloaded multiple times. Switch to OrdinalIgnoreCase for all dedup
collections and the GroupBy in GetDownloadInfoAsync.

Fixes actions#3731

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 8, 2026 04:00
@stefanpenner stefanpenner requested a review from a team as a code owner April 8, 2026 04:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes redundant action resolution/downloads when the same owner/repo@ref is referenced with different casing by switching deduplication to case-insensitive comparisons.

Changes:

  • Makes the batch resolution cache and pending-key set case-insensitive.
  • Makes GetDownloadInfoAsync dedupe/group and return dictionaries case-insensitive.
  • Adds an L0 regression test covering mixed-case references to the same action.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Runner.Worker/ActionManager.cs Updates action resolution/download deduplication to use case-insensitive keying across caches and GetDownloadInfoAsync.
src/Test/L0/Worker/ActionManagerL0.cs Adds regression test asserting mixed-case action references resolve only once.
Comments suppressed due to low confidence (1)

src/Runner.Worker/ActionManager.cs:874

  • Case-insensitive dedup here changes behavior beyond the API call: DownloadRepositoryActionAsync downloads to a directory derived from ActionDownloadInfo.NameWithOwner, but later PrepareRepositoryActionAsync/LoadAction compute the action directory from the original RepositoryPathReference.Name. On case-sensitive filesystems (Linux), if different-cased references are deduped to a single download, subsequent steps may look in a different (non-existent) directory and fail to find action.yml/Dockerfile. The fix likely needs to also normalize the in-memory RepositoryPathReference.Name (or create an alias directory/symlink) so all references resolve to the downloaded directory.
            // Convert to action reference
            var actionReferences = actions
                .GroupBy(x => GetDownloadInfoLookupKey(x), StringComparer.OrdinalIgnoreCase)
                .Where(x => !string.IsNullOrEmpty(x.Key))
                .Select(x =>
                {
                    var action = x.First();
                    var repositoryReference = action.Reference as Pipelines.RepositoryPathReference;
                    ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
                    return new WebApi.ActionReference
                    {
                        NameWithOwner = repositoryReference.Name,
                        Ref = repositoryReference.Ref,
                        Path = repositoryReference.Path,
                    };
                })

On case-sensitive filesystems (Linux), after dedup merges differently-cased
references into one download, PrepareRepositoryActionAsync and LoadAction
would look for directories using the original (non-canonical) casing and
fail to find the action. Fix by normalizing each action step's
repositoryReference.Name to match downloadInfo.NameWithOwner after dedup
lookup, in both the batch and legacy code paths.

Also makes the empty-dictionary return path in GetDownloadInfoAsync
use OrdinalIgnoreCase for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@stefanpenner stefanpenner marked this pull request as draft April 8, 2026 04:32
Replace StringComparer.OrdinalIgnoreCase with ActionLookupKeyComparer
that treats the owner/repo portion of "{owner/repo}@{ref}" keys
case-insensitively while keeping the ref portion case-sensitive, since
git refs are case-sensitive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

Download action repository called repeatedly for actions that have to be the same

2 participants