Skip to content

Bug: resolve_host_repo.cjs resolves to caller repo instead of callee in direct cross-repo workflow_call #21426

@thi-feonir

Description

@thi-feonir

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

  1. 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
  2. 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
  3. 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
    
  4. The Checkout .github and .agents folders step checks out Repo B (caller), not Repo A (callee).

  5. 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

  1. 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
  2. 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
  3. 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:

  1. Compiles a minimal workflow_call markdown workflow with a known source repo
  2. Asserts the activation job's resolve-host-repo step includes GH_AW_HOST_REPO with the correct value
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions