Bug: resolve_host_repo.cjs resolves to caller repo instead of callee repo in direct cross-repo workflow_call invocations
Summary
In gh-aw v0.59.0, when a reusable workflow compiled with gh aw compile is invoked from a different repository via workflow_call: uses:, the resolve_host_repo.cjs runtime script resolves to the caller repository instead of the callee (platform) repository. This causes runtime-import macros (e.g., {{#runtime-import .github/workflows/review-strings.md}}) to fail because the .md file exists in the callee repo but the checkout targeted the caller repo.
This is a different bug from #21188 (which tracks missing authentication for non-public repos). Here, the resolution itself is wrong — the checkout targets the wrong repository entirely, regardless of whether authentication would succeed.
Reproduction
-
Create a reusable workflow in Repo A (e.g., owner/host-repo) using gh aw compile:
- The workflow uses
on: workflow_call with inputs
- The compiled
.lock.yml uses {{#runtime-import .github/workflows/review-strings.md}}
- The
.md file exists in Repo A at .github/workflows/review-strings.md
-
Create a caller workflow in Repo B (e.g., owner/caller-repo):
review-strings:
uses: owner/host-repo/.github/workflows/review-strings.lock.yml@main
with:
pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }}
secrets: inherit
-
Trigger the workflow from Repo B. The resolve-host-repo step outputs:
GITHUB_WORKFLOW_REF: owner/caller-repo/.github/workflows/pull-request-strings.yml@refs/pull/7429/merge
GITHUB_REPOSITORY: owner/caller-repo
Resolved host repo for activation checkout: owner/caller-repo
Same-repo invocation: checking out owner/caller-repo @ refs/pull/7429/merge
-
The Checkout .github and .agents folders step checks out Repo B (caller), not Repo A (callee).
-
The Interpolate variables and render templates step fails:
Error: ERR_API: Failed to process runtime import for .github/workflows/review-strings.md:
ERR_SYSTEM: Runtime import file not found:
/home/runner/work/caller-repo/caller-repo/.github/workflows/review-strings.md
Root Cause
The generateResolveHostRepoStep() in pkg/workflow/compiler_activation_job.go delegates host-repo detection entirely to the resolve_host_repo.cjs runtime script, which parses GITHUB_WORKFLOW_REF to extract the repository.
The Go source comment states:
GITHUB_WORKFLOW_REF always contains the path of the currently executing workflow file (owner/repo/.github/workflows/file.yml@ref), regardless of the triggering event.
This assumption is correct for event-driven relays (e.g., on: issue_comment -> internal workflow_call) where the platform repo's workflow IS the top-level entry point and GITHUB_WORKFLOW_REF correctly points to the platform repo.
However, it is incorrect for direct cross-repo workflow_call invocations where:
- The caller workflow in Repo B is the top-level entry point
- Repo B calls Repo A's reusable workflow via
uses: owner/repo-a/.github/workflows/foo.lock.yml@ref
- GitHub Actions sets
GITHUB_WORKFLOW_REF to the caller's top-level workflow (Repo B), not the callee's reusable workflow (Repo A)
resolve_host_repo.cjs compares the extracted repo with GITHUB_REPOSITORY, finds they match, concludes "same-repo invocation", and resolves to Repo B
This means all cross-repo workflow_call patterns that aren't event-driven relays will misresolve the host repo.
Affected Code
-
pkg/workflow/compiler_activation_job.go - generateResolveHostRepoStep():
- Generates the resolve-host-repo step without injecting any compile-time knowledge about the actual host repo
- The compiler knows the host repo at compile time (it's the repo where
gh aw compile runs), but does not pass this to the runtime script
-
pkg/workflow/js/resolve_host_repo.cjs (runtime):
- Relies solely on
GITHUB_WORKFLOW_REF to determine the host repo
- Has no fallback mechanism when
GITHUB_WORKFLOW_REF points to the caller instead of the callee
-
Generated lock file - The resolve-host-repo step has no env variables providing compile-time repo info:
- name: Resolve host repo for activation checkout
id: resolve-host-repo
uses: actions/github-script@...
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io);
const { main } = require('/opt/gh-aw/actions/resolve_host_repo.cjs');
await main();
Implementation Plan
1. Inject compile-time host repo into the resolve-host-repo step
Edit pkg/workflow/compiler_activation_job.go - Update generateResolveHostRepoStep() to accept the known host repository and emit it as an environment variable:
func (c *Compiler) generateResolveHostRepoStep(hostRepo string) string {
var step strings.Builder
step.WriteString(" - name: Resolve host repo for activation checkout\n")
step.WriteString(" id: resolve-host-repo\n")
step.WriteString(fmt.Sprintf(" uses: %s\n", GetActionPin("actions/github-script")))
if hostRepo != "" {
step.WriteString(" env:\n")
step.WriteString(fmt.Sprintf(" GH_AW_HOST_REPO: \"%s\"\n", hostRepo))
}
step.WriteString(" with:\n")
step.WriteString(" script: |\n")
step.WriteString(generateGitHubScriptWithRequire("resolve_host_repo.cjs"))
return step.String()
}
Update the call site in buildActivationJob() to pass the host repo from WorkflowData:
if hasWorkflowCallTrigger(data.On) && !data.InlinedImports {
compilerActivationJobLog.Print("Adding resolve-host-repo step for workflow_call trigger")
steps = append(steps, c.generateResolveHostRepoStep(data.Repository))
}
If WorkflowData does not currently store the source repository, the compiler should populate it from the Git remote or the --repo flag at compile time.
2. Update resolve_host_repo.cjs to use GH_AW_HOST_REPO as authoritative source
Edit pkg/workflow/js/resolve_host_repo.cjs - When GH_AW_HOST_REPO is set, use it as the authoritative host repo instead of inferring from GITHUB_WORKFLOW_REF:
async function main() {
const workflowRef = process.env.GITHUB_WORKFLOW_REF;
const currentRepo = process.env.GITHUB_REPOSITORY;
// Compile-time host repo takes precedence - this is the repo where
// the .md source and lock.yml were compiled, which is the definitive
// source for runtime-import files.
const hostRepoOverride = process.env.GH_AW_HOST_REPO;
if (hostRepoOverride && hostRepoOverride !== currentRepo) {
// Cross-repo: the compiler told us the host repo
const parts = hostRepoOverride.split('/');
const name = parts.length > 1 ? parts[1] : hostRepoOverride;
core.info(`Compile-time host repo override: ${hostRepoOverride}`);
core.setOutput('target_repo', hostRepoOverride);
core.setOutput('target_repo_name', name);
// Default to the repo's default branch; the caller's uses: clause
// already pinned the lock.yml to a specific ref
core.setOutput('target_ref', '');
return;
}
// Existing GITHUB_WORKFLOW_REF-based logic for event-driven relays...
// (keep current behavior as fallback)
}
3. Add / update unit tests
Edit pkg/workflow/compiler_activation_job_test.go:
- Add
TestGenerateResolveHostRepoStep_InjectsHostRepo: Assert that when a host repo is provided, the generated step includes GH_AW_HOST_REPO env var.
- Add
TestGenerateResolveHostRepoStep_NoHostRepo: Assert backward compatibility - no env block when host repo is empty.
Edit or create JS test file for resolve_host_repo.cjs:
- Add test: When
GH_AW_HOST_REPO is set and differs from GITHUB_REPOSITORY, outputs use the override.
- Add test: When
GH_AW_HOST_REPO is unset, falls back to GITHUB_WORKFLOW_REF parsing (existing behavior).
- Add test: When
GH_AW_HOST_REPO equals GITHUB_REPOSITORY, treats as same-repo (no override needed).
4. Add end-to-end compile regression test
Add a compile-output test that:
- Compiles a minimal
workflow_call markdown workflow with a known source repo
- Asserts the activation job's resolve-host-repo step includes
GH_AW_HOST_REPO with the correct value
- Verifies the checkout step still references
steps.resolve-host-repo.outputs.target_repo
5. Run validation
- Run
make agent-finish to ensure all checks pass
- Run
make recompile to verify no existing workflows regress
- Verify the generated lock file output includes the new
GH_AW_HOST_REPO env var for workflow_call workflows
Additional Context
Bug:
resolve_host_repo.cjsresolves to caller repo instead of callee repo in direct cross-repoworkflow_callinvocationsSummary
In
gh-awv0.59.0, when a reusable workflow compiled withgh aw compileis invoked from a different repository viaworkflow_call: uses:, theresolve_host_repo.cjsruntime script resolves to the caller repository instead of the callee (platform) repository. This causesruntime-importmacros (e.g.,{{#runtime-import .github/workflows/review-strings.md}}) to fail because the.mdfile exists in the callee repo but the checkout targeted the caller repo.This is a different bug from #21188 (which tracks missing authentication for non-public repos). Here, the resolution itself is wrong — the checkout targets the wrong repository entirely, regardless of whether authentication would succeed.
Reproduction
Create a reusable workflow in Repo A (e.g.,
owner/host-repo) usinggh aw compile:on: workflow_callwith inputs.lock.ymluses{{#runtime-import .github/workflows/review-strings.md}}.mdfile exists in Repo A at.github/workflows/review-strings.mdCreate a caller workflow in Repo B (e.g.,
owner/caller-repo):Trigger the workflow from Repo B. The
resolve-host-repostep outputs:The
Checkout .github and .agents foldersstep checks out Repo B (caller), not Repo A (callee).The
Interpolate variables and render templatesstep fails:Root Cause
The
generateResolveHostRepoStep()inpkg/workflow/compiler_activation_job.godelegates host-repo detection entirely to theresolve_host_repo.cjsruntime script, which parsesGITHUB_WORKFLOW_REFto extract the repository.The Go source comment states:
This assumption is correct for event-driven relays (e.g.,
on: issue_comment-> internalworkflow_call) where the platform repo's workflow IS the top-level entry point andGITHUB_WORKFLOW_REFcorrectly points to the platform repo.However, it is incorrect for direct cross-repo
workflow_callinvocations where:uses: owner/repo-a/.github/workflows/foo.lock.yml@refGITHUB_WORKFLOW_REFto the caller's top-level workflow (Repo B), not the callee's reusable workflow (Repo A)resolve_host_repo.cjscompares the extracted repo withGITHUB_REPOSITORY, finds they match, concludes "same-repo invocation", and resolves to Repo BThis means all cross-repo
workflow_callpatterns that aren't event-driven relays will misresolve the host repo.Affected Code
pkg/workflow/compiler_activation_job.go-generateResolveHostRepoStep():gh aw compileruns), but does not pass this to the runtime scriptpkg/workflow/js/resolve_host_repo.cjs(runtime):GITHUB_WORKFLOW_REFto determine the host repoGITHUB_WORKFLOW_REFpoints to the caller instead of the calleeGenerated lock file - The resolve-host-repo step has no env variables providing compile-time repo info:
Implementation Plan
1. Inject compile-time host repo into the resolve-host-repo step
Edit
pkg/workflow/compiler_activation_job.go- UpdategenerateResolveHostRepoStep()to accept the known host repository and emit it as an environment variable:Update the call site in
buildActivationJob()to pass the host repo fromWorkflowData:If
WorkflowDatadoes not currently store the source repository, the compiler should populate it from the Git remote or the--repoflag at compile time.2. Update
resolve_host_repo.cjsto useGH_AW_HOST_REPOas authoritative sourceEdit
pkg/workflow/js/resolve_host_repo.cjs- WhenGH_AW_HOST_REPOis set, use it as the authoritative host repo instead of inferring fromGITHUB_WORKFLOW_REF:3. Add / update unit tests
Edit
pkg/workflow/compiler_activation_job_test.go:TestGenerateResolveHostRepoStep_InjectsHostRepo: Assert that when a host repo is provided, the generated step includesGH_AW_HOST_REPOenv var.TestGenerateResolveHostRepoStep_NoHostRepo: Assert backward compatibility - no env block when host repo is empty.Edit or create JS test file for
resolve_host_repo.cjs:GH_AW_HOST_REPOis set and differs fromGITHUB_REPOSITORY, outputs use the override.GH_AW_HOST_REPOis unset, falls back toGITHUB_WORKFLOW_REFparsing (existing behavior).GH_AW_HOST_REPOequalsGITHUB_REPOSITORY, treats as same-repo (no override needed).4. Add end-to-end compile regression test
Add a compile-output test that:
workflow_callmarkdown workflow with a known source repoGH_AW_HOST_REPOwith the correct valuesteps.resolve-host-repo.outputs.target_repo5. Run validation
make agent-finishto ensure all checks passmake recompileto verify no existing workflows regressGH_AW_HOST_REPOenv var forworkflow_callworkflowsAdditional Context
.github/.agentscheckout withon.github-app#21188 (activation checkout auth for non-public repos), Bug:Checkout actions folderemitted withoutrepository:orref:—Setup Scriptsfails in cross-repo relay #20658 (missing repo/ref on setup-actions checkout), Bug: Activation checkout does not preserve callee workflow ref in caller-hosted relays #20697 (activation checkout ref preservation), Bug: Activation checkout resolves wrong repo/ref in caller-hosted event-driven relays #20696 (wrong repo/ref in event-driven relays)066087f607f52664010289ddd52198f33044c38a)workflow_call(NOT event-driven relay).mdfile to the caller repo at the same path