Repository navigation
Fall back to anonymous REST when SSO blocks actions/* resolution - #75
Conversation
When the authenticated GraphQL path hits SAML enforcement for the actions org, retry those refs using unauthenticated REST. The actions/* repos are public so anonymous access works, avoiding hard failures in environments like Codespaces where the token can't be SSO-authorized for the cross-enterprise actions org.
The initial GraphQL-only fallback missed REST endpoints (ListBranches, ListTags, CompareCommits, Releases, CommitSHA, repoMetadata) and the GraphQL reachability/peel queries. All of these fail identically when the token is SAML-blocked for the actions org. Now any REST 403 that matches SAML enforcement for an SSO-fallback- eligible owner retries the same endpoint anonymously. GraphQL BatchBranchContains falls back to per-branch anonymous REST compare calls. PeelTagObject returns a zero result (callers already handle that gracefully).
Instead of maintaining a static map of eligible orgs, probe the org
with an unauthenticated HEAD /orgs/{owner} request. If it returns 200
the org is publicly visible and anonymous fallback will work. Results
are cached in a sync.Map for the process lifetime.
Stub server returns SAML 403 for authenticated requests (Authorization header present) and 200 with synthetic data for anonymous requests. Validates the full fallback pipeline: GraphQL fails, probe confirms org is public, anonymous REST resolves the action successfully. Also wires the anon HTTP client through the Client struct so it respects GH_ACTIONS_LOCK_INSECURE (TLS skip) in integration tests, and makes anonBase() skip the 'api.' prefix for IP:port hostnames used by the stub server.
IsSAMLEnforcement was requiring api.HTTPError (REST 403) but the GraphQL resolution path produces plain errors via ssoRequiredMessage. Now it also matches plain errors whose message contains SAML enforcement indicators. Also fixed retrySSOWithAnonymous to use IsSAMLEnforcement instead of a hardcoded string check for 'SSO authorization required', which missed the 'SAML enforcement' variant from real GitHub responses. Removed trailing newline in anon_fallback_test.go that failed gofmt.
There was a problem hiding this comment.
⚠️ Not ready to approve
There are a few correctness/operational issues in the new fallback implementation (cached probe behavior, URL path escaping, caching consistency, and error signaling) that should be addressed before merging.
Pull request overview
This PR adds an anonymous REST fallback path when authenticated GitHub API access is blocked by organization SAML/SSO enforcement (notably affecting public actions/* repos in Codespaces-like environments), restoring the ability to resolve and pin actions/* references.
Changes:
- Add an anonymous probe + REST request helpers to retry certain SAML-blocked operations without an
Authorizationheader when the owner is publicly visible. - Wire fallback behavior into GraphQL action-file resolution and several REST-backed endpoints (branches/tags/compare/metadata/releases/commit SHA), plus GraphQL reachability/peel behavior.
- Add unit + integration coverage via a new stubbed scenario (
sso_anon_fallback) and targeted tests for eligibility and resolution behavior.
File summaries
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 4
- Review effort level: Low
Note
Your feedback helps us improve the quality of this feature.
Please use 👍 or 👎 to tell us whether this assessment is correct.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Instead of returning a zero result when SAML blocks the GraphQL peel, actually call the REST git/tags and git/commits endpoints anonymously to determine object type and peel annotated tags to their commit.
- Don't cache false on transport/construction errors in SSOFallbackEligible; only cache after receiving an HTTP response so transient failures don't permanently disable fallback. - Escape file path segments in anonGetFileContent to handle special chars. - Populate branchListCache with anonymous results so singleflight/caching still works on the fallback path. - Return error from anonBatchBranchContains when all compare calls fail instead of silently returning 'no matches'.

Motivation
In Codespaces (and similar environments), the
GITHUB_TOKENcannot be SSO-authorized for cross-enterprise orgs likeactions. This means every API call to resolveactions/checkout,actions/setup-go, etc. fails with a SAML 403 -- even though those repos are fully public. The tool becomes unusable for any workflow that referencesactions/*.Approach
When a SAML enforcement error is detected for a given org, probe that org's public visibility with an unauthenticated
HEAD /orgs/{owner}request. If the org is publicly visible, retry the failed operation anonymously (noAuthorizationheader). This covers:ResolveActionFiles) -- the primary ref-to-SHA + action.yml fetchListBranches,ListTags,CompareCommits,Releases,CommitSHA,repoMetadataBatchBranchContains) -- falls back to per-branch anonymous REST comparePeelTagObject) -- returns zero result on SAML (callers already handle gracefully)The eligibility probe result is cached in a
sync.Mapfor the process lifetime, so each org is only probed once.Key design decisions
anonHTTPclient onClientstruct. RespectsGH_ACTIONS_LOCK_INSECUREfor integration tests (TLS skip), andanonBase()skips theapi.prefix forIP:porthostnames used by the stub server.Testing
TestSSOFallbackEligible,TestResolveActionFiles_SSOFallbackForActionsOrg,TestResolveActionFiles_SSONoFallbackForNonActionsOrgsso_anon_fallback-- stub returns SAML 403 for auth'd requests, 200 for anonymous; validates the full pipeline resolves successfullyapi.github.comthat anonymous resolution ofactions/checkout@v4returns the correct SHA andaction.ymlcontent