-
Notifications
You must be signed in to change notification settings - Fork 279
Description
Feature Request: Cross-repo workflow_call validation and docs
Use Case
We are building a multi-tenant agentic platform in a central platform repo, triggered by users across thousands of application repos. Today an external compute backend bridges the cross-repo trigger gap (webhook → repository_dispatch). A cross-repo workflow_call stub eliminates that entirely:
# .github/workflows/platform-relay.yml — in application repo
on:
issue_comment:
types: [created]
jobs:
relay:
uses: <org>/platform-repo/.github/workflows/platform-gateway.lock.yml@v1
with:
issue_number: ${{ github.event.issue.number }}
source_repo: ${{ github.repository }}
secrets: inheritAdditional and crucial benefit: secrets: inherit gives the caller's COPILOT_GITHUB_TOKEN, so premium requests bill to the caller, not to the platform.
Confirmed Blocker: runtime-import fails cross-repo
The compiled lock file contains a {{#runtime-import .github/workflows/<name>.md}} directive that reads the workflow's Markdown source from the checked-out workspace at runtime. Cross-repo, actions/checkout (without explicit repository:) checks out the caller's repo, because github.repository is the caller in workflow_call context. The platform repo's .md file is not on disk. interpolate_prompt.cjs fails:
ERR_SYSTEM: Runtime import file not found:
/home/runner/work/consumer-repo/consumer-repo/.github/workflows/cross-repo-probe.md
Analysis
Root Cause
generateCheckoutGitHubFolderForActivation() in pkg/workflow/compiler_activation_job.go (line ~429) emits an actions/checkout step without an explicit repository: parameter. In a workflow_call context, this checks out the caller's repo instead of the callee's (platform) repo. The callee's .md files are not on disk, so processRuntimeImport() in actions/setup/js/runtime_import.cjs throws "Runtime import file not found".
Design Decision: Conditional Checkout (not Inlining)
Two approaches were evaluated:
- Compile-time inlining — embed
.mdcontent into the lock file whenworkflow_callis present. Loses edit-without-recompile, increases lock file size. - Conditional callee checkout ✅ — modify the checkout step to use
${{ github.event_name == 'workflow_call' && github.action_repository || github.repository }}. Preserves runtime-import, minimal change, works with mixed triggers.
Option 2 was chosen because:
- It preserves the runtime-import pattern (edit-without-recompile)
- It's a small, additive compiler change
- The explicit
event_namecheck is defensively correct — doesn't depend on knowledge of whengithub.action_repositoryis populated - It works with any trigger combination (
workflow_call+workflow_dispatch,workflow_call+issue_comment, etc.)
Tested Cross-repo Behaviors (all confirmed working)
GITHUB_REPOSITORY= caller's repo ✅GITHUB_ACTION_REPOSITORY= callee repo ✅GITHUB_WORKFLOW_REFcontains callee reference ✅- All
GH_AWvars set ✅ ./actions/setupresolves to pinned remote action in release mode ✅
The only remaining failure mode is the runtime-import step, which this plan fixes.
Implementation Plan
Please implement the following changes:
1. Add hasWorkflowCallTrigger() helper (pkg/workflow/compiler_workflow_call.go)
Add a new exported helper function alongside the existing injectWorkflowCallOutputs() function. Follow the same detection pattern already used at line 31 (strings.Contains(onSection, "workflow_call")):
// hasWorkflowCallTrigger checks if the on section contains a workflow_call trigger.
// Used to detect cross-repo reusable workflow usage for checkout and error handling.
func hasWorkflowCallTrigger(onSection string) bool {
return strings.Contains(onSection, "workflow_call")
}2. Modify activation job checkout (pkg/workflow/compiler_activation_job.go)
In generateCheckoutGitHubFolderForActivation() (currently starts at line ~407):
Add a new conditional block after the existing action-tag check (line ~417) and before the default checkout block (line ~428). When workflow_call is detected in data.On, emit a checkout step with a conditional repository: parameter:
// For workflow_call triggers, checkout the callee repository using a conditional expression.
// github.action_repository points to the callee (platform) repo during workflow_call;
// for other event types the explicit event_name check short-circuits to falsy and we
// fall back to github.repository. This supports mixed triggers (e.g., workflow_call + workflow_dispatch).
if hasWorkflowCallTrigger(data.On) {
compilerActivationJobLog.Print("Adding cross-repo-aware .github checkout for workflow_call trigger")
return []string{
" - name: Checkout .github and .agents folders\n",
fmt.Sprintf(" uses: %s\n", GetActionPin("actions/checkout")),
" with:\n",
" repository: ${{ github.event_name == 'workflow_call' && github.action_repository || github.repository }}\n",
" sparse-checkout: |\n",
" .github\n",
" .agents\n",
" sparse-checkout-cone-mode: true\n",
" fetch-depth: 1\n",
" persist-credentials: false\n",
}
}The existing default checkout block (without repository:) remains unchanged as the fallback for workflows without workflow_call.
3. Add cross-repo secret guidance step (pkg/workflow/compiler_activation_job.go)
In buildActivationJob() (starts at line ~18):
Add a new conditional block after the secret validation step (after line ~61, after the else branch that logs "Skipped validate-secret step"). When workflow_call is a trigger, inject an additional step that only runs on failure in a workflow_call context:
// Add cross-repo setup guidance when workflow_call is a trigger.
// This step only runs when secret validation fails in a workflow_call context,
// providing actionable guidance to the caller team about configuring secrets.
if hasWorkflowCallTrigger(data.On) {
compilerActivationJobLog.Print("Adding cross-repo setup guidance step for workflow_call trigger")
steps = append(steps, " - name: Cross-repo setup guidance\n")
steps = append(steps, " if: failure() && github.event_name == 'workflow_call'\n")
steps = append(steps, " run: |\n")
steps = append(steps, " echo \"::error::COPILOT_GITHUB_TOKEN must be configured in the CALLER repository's secrets.\"\n")
steps = append(steps, " echo \"::error::For cross-repo workflow_call, secrets must be set in the repository that triggers the workflow.\"\n")
steps = append(steps, " echo \"::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup\"\n")
}4. Add tests for trigger detection (pkg/workflow/compiler_workflow_call_test.go)
Add a new test function TestHasWorkflowCallTrigger in the existing test file. Test cases:
workflow_callpresent in map format →trueworkflow_callpresent with inputs →trueworkflow_callabsent (onlypush+workflow_dispatch) →falseworkflow_callwith other triggers (issue_comment+workflow_call) →true- Empty string →
false workflow_dispatchonly (notworkflow_call) →false
5. Add tests for cross-repo checkout generation (pkg/workflow/compiler_activation_job_test.go)
Add a new test function TestGenerateCheckoutGitHubFolderForActivation_WorkflowCall. Test cases:
workflow_calltrigger present → checkout includesrepository:with expressiongithub.event_name == 'workflow_call' && github.action_repository || github.repositoryworkflow_calltrigger with inputs + mixed triggers → same cross-repo checkout- No
workflow_calltrigger → checkout does NOT includerepository:parameter issue_commentonly → checkout does NOT includerepository:- Action-tag specified with
workflow_call→ no checkout emitted (existing behavior preserved)
All test cases should verify:
.githuband.agentsare insparse-checkoutpersist-credentials: falseis presentfetch-depth: 1is present
6. Update glossary (docs/src/content/docs/reference/glossary.md)
Replace the current ### Trigger File entry (line ~217) with an expanded version that mentions cross-repo usage:
A plain GitHub Actions workflow (
.yml) that separates trigger definitions from agentic workflow logic. Calls a compiled orchestrator'sworkflow_callentry point in response to any GitHub event (issues, pushes, labels, manual dispatch). Decouples trigger changes from the compilation cycle — updating when an orchestrator runs requires editing only the trigger file, not recompiling the agentic workflow.Trigger files can live in the same repository as the orchestrator or in a different repository (cross-repo
workflow_call). Cross-repo usage requires the callee repository to be public, internal, or to have explicitly granted Actions access. When usingsecrets: inherit, the caller's secrets are passed through — includingCOPILOT_GITHUB_TOKEN, which must be configured in the caller's repository. See CentralRepoOps.
7. Update central-repo-ops docs (docs/src/content/docs/patterns/central-repo-ops.mdx)
Add a new ## Cross-Repository Trigger File section after the existing Trigger File section. Include:
- Example caller stub — the
platform-relay.ymlYAML shown above - Repository visibility — callee must be public, internal, or have explicitly granted Actions access; explain how to grant access from Settings → Actions → General
- Secrets configuration —
COPILOT_GITHUB_TOKENmust be in the caller repo; explainsecrets: inheritpasses the caller's secrets - Billing — premium Copilot requests bill to the caller's token, not the platform's
- How it works — brief explanation that the compiler detects
workflow_calland generates a cross-repo-aware checkout usinggithub.action_repository
8. Follow Guidelines
- Use error message format:
[what's wrong]. [what's expected]. [example]for any new validation errors - Follow file organization patterns from
scratchpad/code-organization.md - Run
make agent-finishbefore completing (build, test, recompile, format, lint) - No new dependencies required (no license concerns)
What Does NOT Change
- No runtime JS changes (
runtime_import.cjs,interpolate_prompt.cjsuntouched) - No changes to
validate_multi_secret.sh - Same-repo
workflow_callcontinues to work identically - Existing triggers, agent steps, safe-outputs, conclusion logic, lock file format — all untouched
- No new frontmatter fields or compiler flags
Impact Assessment
Low risk. The change is additive:
- Only modifies behavior when
workflow_callis present in theon:section - The conditional expression correctly falls back to
github.repositoryfor all non-workflow_callevents - The cross-repo guidance step only runs on
failure()ANDworkflow_call— invisible in all other cases - Existing compiled lock files are unaffected (no
workflow_call→ no change)