@@ -45,16 +45,17 @@ async function main() {
4545
4646 // Determine workflow source repository from the workflow ref for cross-repo support.
4747 //
48- // For cross-repo workflow_call invocations ( reusable workflows called from another repo),
49- // the GITHUB_WORKFLOW_REF env var always points to the TOP-LEVEL CALLER's workflow, not
50- // the reusable workflow being executed. This causes the script to look for lock files in
51- // the wrong repository.
48+ // For cross-repo reusable workflow invocations, both GITHUB_WORKFLOW_REF (env var) and
49+ // ${{ github.workflow_ref }} (injected as GH_AW_CONTEXT_WORKFLOW_REF) resolve to the
50+ // TOP-LEVEL CALLER's workflow, not the reusable workflow being executed. This causes the
51+ // script to look for lock files in the wrong repository when used alone .
5252 //
53- // The GitHub Actions expression ${{ github.workflow_ref }} is injected as GH_AW_CONTEXT_WORKFLOW_REF
54- // by the compiler and correctly identifies the CURRENT reusable workflow 's ref even in
55- // cross-repo workflow_call scenarios. We prefer it over GITHUB_WORKFLOW_REF when available .
53+ // The reliable fix is the referenced_workflows API lookup below, which identifies the
54+ // callee's repo/ref from the caller 's run object. GH_AW_CONTEXT_WORKFLOW_REF is only
55+ // used as a fallback when the API lookup is unavailable or finds no matching entry .
5656 //
57- // Ref: https://github.com/github/gh-aw/issues/23935
57+ // Refs: https://github.com/github/gh-aw/issues/23935
58+ // https://github.com/github/gh-aw/issues/24422
5859 const workflowEnvRef = process . env . GH_AW_CONTEXT_WORKFLOW_REF || process . env . GITHUB_WORKFLOW_REF || "" ;
5960 const currentRepo = process . env . GITHUB_REPOSITORY || `${ context . repo . owner } /${ context . repo . repo } ` ;
6061
@@ -82,14 +83,18 @@ async function main() {
8283 ref = undefined ;
8384 }
8485
85- // For workflow_call events, use referenced_workflows from the GitHub API run object to
86- // resolve the callee (reusable workflow) repo and ref.
86+ // Attempt referenced_workflows API lookup to detect cross-repo callee repo/ref.
87+ //
88+ // IMPORTANT: GITHUB_EVENT_NAME inside a reusable workflow reflects the ORIGINAL trigger
89+ // event (e.g., "push", "issues"), NOT "workflow_call". We therefore cannot rely on event
90+ // name to detect cross-repo scenarios.
91+ //
92+ // Similarly, GH_AW_CONTEXT_WORKFLOW_REF (${{ github.workflow_ref }}) resolves to the
93+ // CALLER's workflow ref, not the callee's. It is used as a fallback only when the API
94+ // lookup is unavailable or finds no matching entry.
8795 //
8896 // Resolution priority:
8997 // 1. referenced_workflows[].sha — immutable commit SHA from the callee repo (most precise).
90- // GH_AW_CONTEXT_WORKFLOW_REF (${{ github.workflow_ref }}) correctly identifies the callee
91- // in most cases, but referenced_workflows carries the pinned sha which won't drift if a
92- // branch ref moves during a long-running job.
9398 // 2. referenced_workflows[].ref — branch/tag ref from the callee (fallback when sha absent).
9499 // 3. GH_AW_CONTEXT_WORKFLOW_REF — injected by the compiler; used when the API is unavailable
95100 // or when no matching entry is found in referenced_workflows.
@@ -98,60 +103,66 @@ async function main() {
98103 // are set to the caller's run ID and repo. The caller's run object includes a
99104 // referenced_workflows array listing the callee's exact path, sha, and ref.
100105 //
101- // GITHUB_EVENT_NAME and GITHUB_RUN_ID are always set in GitHub Actions environments.
102- // context.eventName / context.runId are fallbacks for environments where env vars are absent.
106+ // Short-circuit: if the env workflow ref already ends with the current workflow file,
107+ // the env vars already correctly identify the source (same-repo or non-reusable run).
108+ // Skip the API call to avoid unnecessary rate-limit usage and permission noise.
109+ //
110+ // GITHUB_RUN_ID is always set in GitHub Actions environments.
111+ // context.runId is a fallback for environments where env vars are absent.
103112 //
104- // Ref : https://github.com/github/gh-aw/issues/24422
105- const eventName = process . env . GITHUB_EVENT_NAME || context . eventName ;
106- if ( eventName === "workflow_call" ) {
107- const runId = parseInt ( process . env . GITHUB_RUN_ID || String ( context . runId ) , 10 ) ;
108- if ( Number . isFinite ( runId ) ) {
109- const [ runOwner , runRepo ] = currentRepo . split ( "/" ) ;
110- try {
111- core . info ( `workflow_call event detected, resolving callee repo via referenced_workflows API (run ${ runId } )` ) ;
112- const runResponse = await github . rest . actions . getWorkflowRun ( {
113- owner : runOwner ,
114- repo : runRepo ,
115- run_id : runId ,
116- } ) ;
117-
118- const referencedWorkflows = runResponse . data . referenced_workflows || [ ] ;
119- core . info ( `Found ${ referencedWorkflows . length } referenced workflow(s) in caller run` ) ;
120-
121- // Find the entry whose path matches the current workflow file.
122- // Path format: "org/repo/.github/workflows/file.lock.yml@ref"
123- // Using replace to robustly strip the optional @ref suffix before matching.
124- const matchingEntry = referencedWorkflows . find ( wf => {
125- const pathWithoutRef = wf . path . replace ( / @ . * $ / , "" ) ;
126- return pathWithoutRef . endsWith ( `/.github/workflows/ ${ workflowFile } ` ) ;
127- } ) ;
128-
129- if ( matchingEntry ) {
130- const pathMatch = matchingEntry . path . match ( GITHUB_REPO_PATH_RE ) ;
131- if ( pathMatch ) {
132- owner = pathMatch [ 1 ] ;
133- repo = pathMatch [ 2 ] ;
134- // Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref.
135- ref = matchingEntry . sha || matchingEntry . ref || pathMatch [ 3 ] ;
136- workflowRepo = ` ${ owner } / ${ repo } ` ;
137- core . info ( `Resolved callee repo from referenced_workflows: ${ owner } / ${ repo } @ ${ ref || "(default branch)" } ` ) ;
138- core . info ( ` Referenced workflow path: ${ matchingEntry . path } ` ) ;
139- }
140- } else {
141- core . info ( `No matching entry in referenced_workflows for " ${ workflowFile } ", falling back to GH_AW_CONTEXT_WORKFLOW_REF ` ) ;
113+ // Refs : https://github.com/github/gh-aw/issues/24422
114+ const runId = parseInt ( process . env . GITHUB_RUN_ID || String ( context . runId ) , 10 ) ;
115+ const envRefWithoutAt = workflowEnvRef . replace ( / @ . * $ / , "" ) ;
116+ const envRefMatchesWorkflow = envRefWithoutAt . endsWith ( `/.github/workflows/ ${ workflowFile } ` ) ;
117+
118+ if ( envRefMatchesWorkflow ) {
119+ core . info ( "Env workflow ref already identifies this workflow, skipping referenced_workflows API lookup" ) ;
120+ } else if ( Number . isFinite ( runId ) ) {
121+ const [ runOwner , runRepo ] = currentRepo . split ( "/" ) ;
122+ try {
123+ core . info ( `Checking for cross-repo callee via referenced_workflows API (run ${ runId } )` ) ;
124+ const runResponse = await github . rest . actions . getWorkflowRun ( {
125+ owner : runOwner ,
126+ repo : runRepo ,
127+ run_id : runId ,
128+ } ) ;
129+
130+ const referencedWorkflows = runResponse . data . referenced_workflows || [ ] ;
131+ core . info ( `Found ${ referencedWorkflows . length } referenced workflow(s) in run` ) ;
132+
133+ // Find the entry whose path matches the current workflow file.
134+ // Path format: "org/repo/.github/workflows/file.lock.yml@ref"
135+ // Using replace to robustly strip the optional @ref suffix before matching.
136+ const matchingEntry = referencedWorkflows . find ( wf => {
137+ const pathWithoutRef = wf . path . replace ( / @ . * $ / , "" ) ;
138+ return pathWithoutRef . endsWith ( `/.github/workflows/ ${ workflowFile } ` ) ;
139+ } ) ;
140+
141+ if ( matchingEntry ) {
142+ const pathMatch = matchingEntry . path . match ( GITHUB_REPO_PATH_RE ) ;
143+ if ( pathMatch ) {
144+ owner = pathMatch [ 1 ] ;
145+ repo = pathMatch [ 2 ] ;
146+ // Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref.
147+ ref = matchingEntry . sha || matchingEntry . ref || pathMatch [ 3 ] ;
148+ workflowRepo = ` ${ owner } / ${ repo } ` ;
149+ core . info ( `Resolved callee repo from referenced_workflows: ${ owner } / ${ repo } @ ${ ref || "(default branch)" } ` ) ;
150+ core . info ( ` Referenced workflow path: ${ matchingEntry . path } ` ) ;
142151 }
143- } catch ( error ) {
144- core . info ( `Could not fetch referenced_workflows from API: ${ getErrorMessage ( error ) } , falling back to GH_AW_CONTEXT_WORKFLOW_REF` ) ;
152+ } else {
153+ core . info ( `No matching entry in referenced_workflows for " ${ workflowFile } " , falling back to GH_AW_CONTEXT_WORKFLOW_REF` ) ;
145154 }
146- } else {
147- core . info ( "workflow_call event detected but run ID is unavailable or invalid , falling back to GH_AW_CONTEXT_WORKFLOW_REF" ) ;
155+ } catch ( error ) {
156+ core . info ( `Could not fetch referenced_workflows from API: ${ getErrorMessage ( error ) } , falling back to GH_AW_CONTEXT_WORKFLOW_REF` ) ;
148157 }
158+ } else {
159+ core . info ( "Run ID is unavailable or invalid, falling back to GH_AW_CONTEXT_WORKFLOW_REF" ) ;
149160 }
150161
151162 const contextWorkflowRef = process . env . GH_AW_CONTEXT_WORKFLOW_REF ;
152163 core . info ( `GITHUB_WORKFLOW_REF: ${ process . env . GITHUB_WORKFLOW_REF || "(not set)" } ` ) ;
153164 if ( contextWorkflowRef ) {
154- core . info ( `GH_AW_CONTEXT_WORKFLOW_REF: ${ contextWorkflowRef } (used for source repo resolution )` ) ;
165+ core . info ( `GH_AW_CONTEXT_WORKFLOW_REF: ${ contextWorkflowRef } (available as env fallback )` ) ;
155166 }
156167 core . info ( `GITHUB_REPOSITORY: ${ currentRepo } ` ) ;
157168 core . info ( `Resolved source repo: ${ owner } /${ repo } @ ${ ref || "(default branch)" } ` ) ;
0 commit comments