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:
acc.agentFile and acc.agentImportSpec stay empty.
- In
pkg/workflow/compiler_yaml_main_job.go, needsGithubMerge := (len(data.RepositoryImports) > 0) || (data.AgentFile != "" && data.AgentImportSpec != "") evaluates to false.
- 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
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.
- 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.
- 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
Summary
On Windows,
gh aw compilesilently omits theMerge remote .github folderstep from generated.lock.ymlfiles 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 samegh-awversion. This makes thegh-aw-lockfile-checkCI 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.:
Compare against the lockfile produced by the same version on Linux. The Windows lockfile is missing:
The bug is also reproducible against
mainas of today (2026-05-08).Root cause
pkg/parser/import_bfs.go(line 187 onmain):item.fullPathis built earlier inResolveIncludePath(pkg/parser/remote_fetch.go) viafilepath.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 atstrings.Index(item.fullPath, "/.github/").Consequences:
acc.agentFileandacc.agentImportSpecstay empty.pkg/workflow/compiler_yaml_main_job.go,needsGithubMerge := (len(data.RepositoryImports) > 0) || (data.AgentFile != "" && data.AgentImportSpec != "")evaluates tofalse.Merge remote .github folderstep is silently dropped.Existing test that catches this
pkg/parser/agent_import_integration_test.go::TestAgentImportWithToolsArrayis already a regression test for this exact code path. It is currently green only because theintegrationbuild tag isn't run on Windows in CI. Locally on Windows:Proposed fix
Normalize
item.fullPathwithfilepath.ToSlashbefore the substring checks, mirroring the pattern already used incomputeImportRelPath(pkg/parser/import_field_extractor.go:741):A working patch is on my fork: trask@bc3dbee. With that change applied on Windows,
TestAgentImportWithToolsArraypasses andgh aw compileproduces a lockfile byte-identical to the Linux output.Implementation plan
pkg/parser/import_bfs.go: inprocessImportsFromFrontmatterWithManifestAndSource(around theisAgentFilecheck at line 187), introducefullPathSlash := filepath.ToSlash(item.fullPath)and use it for the/.github/agents/and/.github/substring checks plus for assigningacc.agentFileandimportRelPath. Keep the originalitem.fullPathonly for log lines that intentionally show the OS path.pkg/parserintegration tests (build tagintegration) run on a Windows runner. A small follow-up to whatever workflow runsgo test -tags=integrationis enough — without that, the same class of forward-slash assumptions can regress unnoticed.TestAgentImportWithToolsArrayalready covers it. Optional: add a non-integration unit test that exercisesprocessImportsFromFrontmatterWithManifestAndSourcewith a synthesizeditem.fullPaththat contains backslashes, so the behavior is verified on Linux runners too.Out of scope (separate issue)
While investigating, I noticed
--schedule-seedis silently overridden later inpkg/cli/compile_workflow_processor.go:90-94by the per-filegetRepositorySlugFromRemoteForPathcall. That is a different bug (related to #31018) and I'll file it separately if it isn't already tracked.Environment
gh awv0.71.5(andmainas of 2026-05-08)