Fix case-insensitive dedup of action downloads#4326
Draft
stefanpenner wants to merge 3 commits intoactions:mainfrom
Draft
Fix case-insensitive dedup of action downloads#4326stefanpenner wants to merge 3 commits intoactions:mainfrom
stefanpenner wants to merge 3 commits intoactions:mainfrom
Conversation
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>
Contributor
There was a problem hiding this comment.
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
GetDownloadInfoAsyncdedupe/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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
actions/checkoutandactIONS/checkoutare correctly treated as the same action and downloaded only onceActionLookupKeyComparerthat treats the owner/repo portion of lookup keys case-insensitively while keeping the ref portion case-sensitive (git refs are case-sensitive)NormalizeRepositoryReference) so that directory paths are consistent on case-sensitive filesystems (Linux)Fixes #3731
Changes
ActionLookupKeyComparer— customIEqualityComparer<string>for"{owner/repo}@{ref}"keys: name portion is case-insensitive, ref portion is case-sensitiveGroupByinGetDownloadInfoAsync, and returned download info dictionary all use the new comparerNormalizeRepositoryReference— after dedup lookup, updates each action step'srepositoryReference.Nameto match the canonicaldownloadInfo.NameWithOwner, preventing directory mismatches on LinuxPrepareActions_DeduplicatesCaseInsensitiveActionReferencesverifies three differently-cased references resolve once, download succeeds, and references are normalizedTest plan
PrepareActions_DeduplicatesCaseInsensitiveActionReferencespassesActionManagerL0tests passactions/checkoutvsactIONS/checkout) that the action is downloaded only once🤖 Generated with Claude Code