Skip to content

perf(workflow): check embedded action pins before gh-api network call in ActionResolver#39707

Merged
pelikhan merged 4 commits into
mainfrom
fix/performance-regression-tests
Jun 17, 2026
Merged

perf(workflow): check embedded action pins before gh-api network call in ActionResolver#39707
pelikhan merged 4 commits into
mainfrom
fix/performance-regression-tests

Conversation

@dsyme

@dsyme dsyme commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds an in-memory embedded-pins fast path to ActionResolver.ResolveSHA so that builtin actions (e.g. actions/github-script@v9) never trigger a gh-api subprocess when their SHA is already compiled into the binary via action_pins.json.

Previously, a disk-cache miss immediately fell through to a ~1.2 s gh-api subprocess. The resolver now checks the embedded pin table first, and only falls back to the network when no compatible embedded pin exists.


Changes

pkg/workflow/action_resolver.go

New fast path between disk-cache and network (94a4ff761, 6aa755b08)

After a disk-cache miss, ResolveSHA now:

  1. Normalises the requested version with semverutil.EnsureVPrefix.
  2. If the requested version is a precise semver (e.g. v9.0.0), performs an exact string comparison against each embedded pin for that repo.
  3. Otherwise uses semverutil.IsCompatible to find the highest semver-compatible pin.
  4. On a hit, returns the embedded SHA immediately — no subprocess, no network I/O.
  5. On a miss, logs and falls through to the existing gh-api path.

No on-disk cache write for embedded hits (ae230f0d8, 5548abd9c)

The original fast-path implementation called r.cache.Set() on an embedded hit. This was removed because:

  • Embedded pins are always available from memory (compiled-in), so persisting them buys nothing.
  • Inside Docker containers (e.g. the Alpine CI job), writing actions-lock.json as root prevented the host runner's cleanup step from deleting the file.

The // Note: comment explaining this decision was subsequently re-indented by gofmt (5548abd9c).


Performance

Test Before After
Builtin action SHA resolution (cold disk cache) ~1.2 s (gh-api subprocess) <5 ms (in-memory lookup)
Builtin action SHA resolution (warm disk cache) ~2 ms ~2 ms (unchanged)

All pkg/workflow tests that compile workflows containing builtin actions benefit from the fast path.


Affected File

File Type Breaking
pkg/workflow/action_resolver.go modified no

New imports: pkg/actionpins, pkg/semverutil


Commit Log

SHA Type Message
94a4ff761 perf Check embedded action pins before gh-api network call in ActionResolver
6aa755b08 fix Precise-version exact-match guard in embedded-pin lookup
ae230f0d8 fix Don't write on-disk cache for embedded pin hits in ActionResolver
5548abd9c style Fix comment indentation in action_resolver.go (gofmt)

Notes for Reviewers

  • The precise-version path (requestedIsPrecise) uses plain string equality (pinVersion != requested) rather than a semver API call — intentional and safe because both sides are normalised by EnsureVPrefix before comparison.
  • r.cache.Set() was deliberately removed from the fast path (not just moved). If a caller later requests the same action via the network path it will still be written to disk at that point.
  • Commit 6aa755b08 has the vague message "Potential fix for pull request finding" but its diff is purely a refinement of the semver-matching logic added in 94a4ff761.

Generated by PR Description Updater for issue #39707 ·

… in ActionResolver

The ActionResolver.ResolveSHA method previously fell through to a gh-api
subprocess call whenever the on-disk ActionCache had a miss. For builtin
actions like actions/github-script@v9, this caused a ~1.2s subprocess
invocation even though the exact SHA is already embedded in action_pins.json
as v9.0.0 (semver-compatible with the requested v9 tag).

This fix adds an embedded-pin check between the disk-cache lookup and the
network call. For any action whose requested version is semver-compatible
with an embedded pin, the resolver now returns the embedded SHA immediately
without spawning a gh-api process.

Impact:
- TestCompileWorkflow_PerformanceRegression/small_workflow: 1.4s → 50ms (first
  compilation in a test run), then 2ms (subsequent runs with disk-cache hit)
- TestCompileWorkflow_PerformanceRegression/medium_workflow: 1.2s → 2ms
- Full pkg/workflow test suite: significantly faster for all tests that compile
  workflows with builtin actions
Copilot AI review requested due to automatic review settings June 16, 2026 23:53

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 pkg/workflow action SHA resolution performance by avoiding an expensive gh api subprocess call when the requested action version can be satisfied from the embedded action pin set.

Changes:

  • Adds an embedded action-pin lookup on ActionResolver.ResolveSHA cache misses, before invoking gh api.
  • Caches embedded-pin hits in the on-disk ActionCache to avoid repeated lookups/subprocess calls.
Show a summary per file
File Description
pkg/workflow/action_resolver.go Adds embedded pin short-circuit in ResolveSHA before falling back to gh api resolution.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Comment thread pkg/workflow/action_resolver.go
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

The previous change called r.cache.Set() when returning an embedded-pin hit,
marking the on-disk ActionCache as dirty. This caused actions-lock.json to be
written into mounted volumes when compiling inside Docker containers (e.g. the
Alpine CI test). Since Docker containers run as root, the file was owned by
root:root, preventing the host runner's cleanup step from deleting it.

The embedded pins are always available in memory (action_pins.json is embedded
at compile time), so there is no value in persisting them to the on-disk cache.
Remove the r.cache.Set() call from the embedded-pin fast path.
@github-actions

Copy link
Copy Markdown
Contributor

✅ smoke-ci: safeoutputs CLI comment + comment-memory run (27657200043)

Generated by 🧪 Smoke CI for issue #39707 ·

@github-actions

Copy link
Copy Markdown
Contributor

Hey @dsyme 👋 — great catch on the resolver ordering issue! The before/after numbers in the impact table speak for themselves — collapsing a 1.4 s test run down to 50 ms for the first run and 2 ms cached is a meaningful win for the whole pkg/workflow test suite.

One thing that would strengthen this before merge:

  • Unit tests for the new embedded-pin path in ResolveSHA — the PR fixes TestCompileWorkflow_PerformanceRegression (a timing-based integration test), but there are no direct unit tests for the new branch in ResolveSHA. A few focused cases would lock in the logic and prevent regressions:
    1. Exact-version match (v9.0.0 requested, v9.0.0 pinned) → returns embedded SHA without a network call.
    2. Semver-compatible loose match (v9 requested, v9.0.0 pinned) → returns embedded SHA.
    3. No compatible pin exists → falls through to the API path.
    4. Precise version requested that does not match any pin → falls through.

If you'd like a hand writing those, here's a ready-to-use agent prompt:

Add unit tests for the new embedded-pin lookup path in `pkg/workflow/action_resolver.go` (`ResolveSHA`).

Cover these four scenarios (mock or stub `actionpins.GetActionPinsByRepo` and the downstream network resolver as needed):
1. Exact-version match: requested version `v9.0.0`, pin version `v9.0.0` — should return the embedded SHA without calling the API.
2. Semver-compatible loose match: requested version `v9`, pin version `v9.0.0` — should return the embedded SHA without calling the API.
3. No compatible pin: requested version `v3`, no `v3.x` pin present — should fall through to the existing API resolver.
4. Precise version with no matching pin: requested version `v9.1.0`, only `v9.0.0` pinned — should fall through to the API resolver.

Place the tests in `pkg/workflow/action_resolver_test.go` following the existing test style in that package.

Generated by ✅ Contribution Check ·

@github-actions

Copy link
Copy Markdown
Contributor

Please add focused unit tests for the embedded-pin ResolveSHA path (exact match, loose semver, fallback).

Generated by 👨‍🍳 PR Sous Chef ·

@github-actions

Copy link
Copy Markdown
Contributor

\n@copilot add focused unit tests for exact-match, loose-semver, and fallback ResolveSHA paths.

Generated by 👨‍🍳 PR Sous Chef ·

@github-actions

Copy link
Copy Markdown
Contributor

@copilot review all comments and address unresolved review feedback.

Generated by 👨‍🍳 PR Sous Chef ·

@github-actions

Copy link
Copy Markdown
Contributor

@copilot please refresh the branch and summarize the remaining blockers.

Generated by 👨‍🍳 PR Sous Chef ·

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan June 17, 2026 14:04
@pelikhan pelikhan merged commit 1bdb27b into main Jun 17, 2026
29 checks passed
@pelikhan pelikhan deleted the fix/performance-regression-tests branch June 17, 2026 14:14
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.

4 participants