Skip to content

Bug: gh aw compile on Windows silently drops the Merge remote .github folder step for agent imports #31097

@trask

Description

@trask

Summary

On Windows, gh aw compile silently omits the Merge remote .github folder step from generated .lock.yml files when a workflow imports a custom agent under .github/agents/*.md. The compiler does not error or warn — the produced lockfile simply differs from one compiled on Linux at the same gh-aw version. This makes the gh-aw-lockfile-check CI job fail for any contributor who recompiles a workflow on Windows.

I hit this in open-telemetry/opentelemetry-java-instrumentation on gh-aw v0.71.5.

Reproduction

On a Windows host (no WSL), inside any repo containing a workflow that imports an agent file, e.g.:

# .github/workflows/foo.md
imports:
  - .github/agents/foo.agent.md
gh extension install github/gh-aw --pin v0.71.5
gh aw compile foo

Compare against the lockfile produced by the same version on Linux. The Windows lockfile is missing:

- name: Merge remote .github folder
  uses: actions/github-script@...
  env:
    GH_AW_AGENT_FILE: ".github/agents/foo.agent.md"
    GH_AW_AGENT_IMPORT_SPEC: ".github/agents/foo.agent.md"
  with:
    script: |
      ...
      const { main } = require('${{ runner.temp }}/gh-aw/actions/merge_remote_agent_github_folder.cjs');
      await main();

The bug is also reproducible against main as of today (2026-05-08).

Root cause

pkg/parser/import_bfs.go (line 187 on main):

isAgentFile := strings.Contains(item.fullPath, "/.github/agents/") &&
               strings.HasSuffix(strings.ToLower(item.fullPath), ".md")

item.fullPath is built earlier in ResolveIncludePath (pkg/parser/remote_fetch.go) via filepath.Join, which uses the OS-native separator. On Windows it contains backslashes, so the literal "/.github/agents/" substring never matches. The same forward-slash assumption is reused a few lines below at strings.Index(item.fullPath, "/.github/").

Consequences:

  1. acc.agentFile and acc.agentImportSpec stay empty.
  2. In pkg/workflow/compiler_yaml_main_job.go, needsGithubMerge := (len(data.RepositoryImports) > 0) || (data.AgentFile != "" && data.AgentImportSpec != "") evaluates to false.
  3. The Merge remote .github folder step is silently dropped.

Existing test that catches this

pkg/parser/agent_import_integration_test.go::TestAgentImportWithToolsArray is already a regression test for this exact code path. It is currently green only because the integration build tag isn't run on Windows in CI. Locally on Windows:

$ go test -tags=integration ./pkg/parser/ -run TestAgentImportWithToolsArray
--- FAIL: TestAgentImportWithToolsArray
    agent_import_integration_test.go:92: Expected AgentFile to be set, got empty string
    agent_import_integration_test.go:97: AgentFile = "", want ".github/agents/feature-flag-remover.agent.md"

Proposed fix

Normalize item.fullPath with filepath.ToSlash before the substring checks, mirroring the pattern already used in computeImportRelPath (pkg/parser/import_field_extractor.go:741):

fullPathSlash := filepath.ToSlash(item.fullPath)
isAgentFile := strings.Contains(fullPathSlash, "/.github/agents/") &&
               strings.HasSuffix(strings.ToLower(fullPathSlash), ".md")
if isAgentFile {
    ...
    if idx := strings.Index(fullPathSlash, "/.github/"); idx >= 0 {
        acc.agentFile = fullPathSlash[idx+1:]
        importRelPath = acc.agentFile
    } else {
        acc.agentFile = fullPathSlash
        importRelPath = fullPathSlash
    }
    ...
}

A working patch is on my fork: trask@bc3dbee. With that change applied on Windows, TestAgentImportWithToolsArray passes and gh aw compile produces a lockfile byte-identical to the Linux output.

Implementation plan

  1. pkg/parser/import_bfs.go: in processImportsFromFrontmatterWithManifestAndSource (around the isAgentFile check at line 187), introduce fullPathSlash := filepath.ToSlash(item.fullPath) and use it for the /.github/agents/ and /.github/ substring checks plus for assigning acc.agentFile and importRelPath. Keep the original item.fullPath only for log lines that intentionally show the OS path.
  2. CI coverage: ensure the pkg/parser integration tests (build tag integration) run on a Windows runner. A small follow-up to whatever workflow runs go test -tags=integration is enough — without that, the same class of forward-slash assumptions can regress unnoticed.
  3. Tests: no new tests strictly required because TestAgentImportWithToolsArray already covers it. Optional: add a non-integration unit test that exercises processImportsFromFrontmatterWithManifestAndSource with a synthesized item.fullPath that contains backslashes, so the behavior is verified on Linux runners too.

Out of scope (separate issue)

While investigating, I noticed --schedule-seed is silently overridden later in pkg/cli/compile_workflow_processor.go:90-94 by the per-file getRepositorySlugFromRemoteForPath call. That is a different bug (related to #31018) and I'll file it separately if it isn't already tracked.

Environment

  • gh aw v0.71.5 (and main as of 2026-05-08)
  • Windows 11, git-bash, Go 1.22

Metadata

Metadata

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