Skip to content

Fix nested remote imports to resolve against parent workflowspec base path#16550

Merged
pelikhan merged 3 commits intomainfrom
copilot/fix-nested-remote-imports
Feb 18, 2026
Merged

Fix nested remote imports to resolve against parent workflowspec base path#16550
pelikhan merged 3 commits intomainfrom
copilot/fix-nested-remote-imports

Conversation

Copy link
Contributor

Copilot AI commented Feb 18, 2026

Nested relative imports from remote workflowspecs outside .github/workflows/ were resolving against a hardcoded .github/workflows/ base instead of the parent's actual base directory. This caused cache path inconsistencies and security scanner warnings for non-existent files.

Example:

Parent:  elastic/ai-github-actions/gh-agent-workflows/gh-aw-workflows/test.md@sha
Nested:  gh-aw-fragments/elastic-tools.md

Before:  elastic/ai-github-actions/.github/workflows/gh-aw-fragments/elastic-tools.md@sha
After:   elastic/ai-github-actions/gh-agent-workflows/gh-aw-fragments/elastic-tools.md@sha

Changes:

  • Added BasePath field to remoteImportOrigin tracking the parent workflowspec's base directory
  • Updated parseRemoteOrigin() to derive base path from workflowspec (full directory path containing the file)
  • Modified nested import resolution to use parent's BasePath instead of hardcoded .github/workflows/
  • Falls back to .github/workflows when BasePath is empty (backward compatibility)
  • Added path.Clean() to normalize BasePath (removes ./ components and redundant slashes)
  • Added detailed logging for BasePath derivation and nested import resolution
  • Added comprehensive tests for path cleaning, URL-like paths, and enterprise domain workflowspecs

Path Cleaning:
Paths are normalized using path.Clean() to handle edge cases:

Input:  owner/repo/./workflows//subdir///test.md@main
Output: BasePath = "workflows/subdir" (cleaned)

Enterprise Domain Support:
Enterprise GitHub instances use the same owner/repo/path workflowspec format. The GitHub domain is handled by the GH_HOST environment variable, not in the workflowspec itself.

Testing:

  • ✅ All existing tests updated and passing
  • ✅ Added 5 new test cases for path cleaning scenarios
  • ✅ Added tests documenting URL-like path behavior
  • ✅ Added test validating enterprise domain support
Original prompt

This section details on the original issue you should resolve

<issue_title>Nested remote imports resolve against hardcoded .github/workflows/ instead of parent workflowspec base path</issue_title>
<issue_description>## Bug

When a consumer shim imports a remote workflowspec whose repo-relative path is not under .github/workflows/ (e.g., gh-agent-workflows/gh-aw-workflows/test-improvement-rwxp.md), the compiler resolves nested relative imports against a hardcoded .github/workflows/ base instead of the parent workflowspec's actual base directory.

This produces inconsistent runtime-import cache paths in the lock file and causes security scanner warnings for files that don't exist locally.

Reproduction

In a consumer repo with this shim (test-improvement.md):

imports:
- elastic/ai-github-actions/gh-agent-workflows/gh-aw-workflows/test-improvement-rwxp.md@<sha>

The remote prompt file imports fragments:

imports:
  - gh-aw-fragments/elastic-tools.md
  - gh-aw-fragments/formatting.md
  ...

Running gh aw compile produces:

WARNING: Skipping security scan for unresolvable import 'gh-aw-fragments/elastic-tools.md': file not found: .../.github/workflows/gh-aw-fragments/elastic-tools.md
WARNING: Skipping security scan for unresolvable import 'gh-aw-fragments/formatting.md': ...

And the lock file has inconsistent runtime-import paths:

# Top-level import — cached via gh-agent-workflows/ (correct, matches workflowspec):
{{#runtime-import .github/aw/imports/elastic/ai-github-actions/<sha>/gh-agent-workflows_gh-aw-workflows_test-improvement-rwxp.md}}

# Nested import — cached via .github/workflows/ (WRONG, doesn't match parent's base):
{{#runtime-import .github/aw/imports/elastic/ai-github-actions/<sha>/.github_workflows_gh-aw-fragments_elastic-tools.md}}

Compare with a working workflow (mention-in-pr) whose shim imports through .github/workflows/:

# Both top-level and nested are consistent:
{{#runtime-import .github/aw/imports/elastic/ai-github-actions/<sha>/.github_workflows_gh-aw-workflows_mention-in-pr-rwxp.md}}
{{#runtime-import .github/aw/imports/elastic/ai-github-actions/<sha>/.github_workflows_gh-aw-fragments_elastic-tools.md}}

Root Cause

In import_processor.go, the nested import resolution hardcodes .github/workflows/:

resolvedPath = fmt.Sprintf("%s/%s/.github/workflows/%s@%s",
    item.remoteOrigin.Owner, item.remoteOrigin.Repo, cleanPath, item.remoteOrigin.Ref)

This was introduced in #15987.

Fix

Derive the base path from the parent workflowspec instead of hardcoding .github/workflows/. Since imports are always 2-level (dir/file.md), the base is everything between owner/repo/ and the last two path components of the workflowspec.

For elastic/ai-github-actions/gh-agent-workflows/gh-aw-workflows/test-improvement-rwxp.md@sha:

  • repo path parts: ["gh-agent-workflows", "gh-aw-workflows", "test-improvement-rwxp.md"]
  • base = parts before last 2 = "gh-agent-workflows"
  • nested gh-aw-fragments/elastic-tools.mdelastic/ai-github-actions/gh-agent-workflows/gh-aw-fragments/elastic-tools.md@sha

Add a BasePath field to remoteImportOrigin and use it for nested resolution. This also avoids unnecessary symlink resolution API calls when the workflowspec already points at the real file locations.</issue_description>

Comments on the Issue (you are @copilot in this section)

@pelikhan You shall be forgiven as long as you add more tests :) @dsyme @strawgate I added more integration tests for adding/compiling workflows with quite complex `import` in #16437

I also fixed one case of a/b/c.md remote imports yesterday - not sure if it was this case or not though</comment_new>
<comment_new>@pelikhan
See githubnext/agentics@6c7124b</comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@pelikhan
Copy link
Contributor

@copilot: clean paths, add logging, add tests

…solution

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix nested remote imports resolving against hardcoded path Fix nested remote imports to resolve against parent workflowspec base path Feb 18, 2026
Copilot AI requested a review from pelikhan February 18, 2026 13:05
@pelikhan
Copy link
Contributor

@copilot validate full URLs and enterprise domains are supported

@pelikhan
Copy link
Contributor

@copilot compare fix to #16372

…Path

- Clean BasePath using path.Clean() to normalize paths (remove ./ and redundant slashes)
- Add detailed logging for BasePath derivation with spec, owner, repo, ref
- Add logging when no BasePath can be derived (file at repo root)
- Clean basePath before use in nested import resolution
- Add tests for path cleaning (./ components, redundant slashes, complex paths)
- Add tests for URL-like paths (document current behavior)
- Add test for enterprise domain workflowspecs
- Fix BasePath derivation to include full directory path (not just parent dir)

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 18, 2026

@copilot: clean paths, add logging, add tests

Done in commit d1227cc:

  • Added path.Clean() to normalize BasePath (removes ./ and redundant slashes)
  • Added detailed logging for BasePath derivation with spec, owner, repo, ref
  • Added logging when no BasePath can be derived
  • Added 5 new test cases for path cleaning, URL handling, and enterprise domains

@copilot validate full URLs and enterprise domains are supported

Validated in commit d1227cc:

  • Added test confirming enterprise domains use the same owner/repo/path format (domain handled by GH_HOST env var)
  • Added test documenting current behavior with URL-like paths (they're accepted by isWorkflowSpec but may parse incorrectly)
  • Full URLs are not the intended workflowspec format - documented current state for future handling if needed

Copy link
Contributor Author

Copilot AI commented Feb 18, 2026

@copilot compare fix to #16372

These are complementary fixes solving different problems:

PR #16372 (resolveRemoteSymlinks):

  • Solves: GitHub API doesn't follow filesystem symlinks
  • Example: .github/workflows/shared../../gh-agent-workflows/shared (symlink)
  • Layer: File download/retrieval (triggered on 404)
  • Checks for symlinks via GitHub API and retries with resolved path

My PR (BasePath):

  • Solves: Nested imports resolve against hardcoded .github/workflows/ instead of parent's actual directory
  • Example: Parent at gh-agent-workflows/gh-aw-workflows/test.md, nested import should resolve relative to gh-agent-workflows/gh-aw-workflows/, not .github/workflows/
  • Layer: Import path resolution (always runs during import graph traversal)
  • Uses path string manipulation to derive and apply correct base directory

Both needed:

  1. BasePath determines WHERE to look for nested imports (correct directory)
  2. resolveRemoteSymlinks helps FIND the file if that directory is a symlink

They work at different layers and solve orthogonal problems.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh gh pr view 16372 --json title,body,files (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan pelikhan marked this pull request as ready for review February 18, 2026 13:21
Copilot AI review requested due to automatic review settings February 18, 2026 13:21
@pelikhan pelikhan merged commit 0b9dfa6 into main Feb 18, 2026
129 checks passed
@pelikhan pelikhan deleted the copilot/fix-nested-remote-imports branch February 18, 2026 13:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug where nested remote imports from workflowspecs outside .github/workflows/ were incorrectly resolved against a hardcoded .github/workflows/ base path instead of the parent workflowspec's actual base directory. This caused cache path inconsistencies and security scanner warnings for non-existent files.

Changes:

  • Added BasePath field to remoteImportOrigin to track the parent workflowspec's base directory path
  • Updated parseRemoteOrigin() to derive BasePath from the workflowspec by extracting the directory path containing the file
  • Modified nested import resolution logic to use the parent's BasePath instead of hardcoded .github/workflows/, with fallback for backward compatibility
  • Added comprehensive test coverage including path cleaning, edge cases, and documentation of URL-like path behavior

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
pkg/parser/import_processor.go Added BasePath field to remoteImportOrigin struct, implemented BasePath derivation in parseRemoteOrigin(), and updated nested import resolution to use parent's BasePath instead of hardcoded .github/workflows/
pkg/parser/import_remote_nested_test.go Updated all existing tests to verify BasePath field, added new tests for path cleaning scenarios (./components, redundant slashes), URL-like path behavior documentation, and enterprise domain support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

// Derive BasePath: everything between owner/repo and the last component (filename)
// Since imports are always 2-level (dir/file.md), the base is everything before the filename
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment "Since imports are always 2-level (dir/file.md)" is inaccurate. The test cases demonstrate that imports can have arbitrary depth (e.g., "owner/repo/a/b/c/d/e/file.md" has BasePath "a/b/c/d/e" which is 5 levels deep). The comment should be updated to reflect that the BasePath is the directory path containing the file, which can have any number of levels.

Suggested change
// Since imports are always 2-level (dir/file.md), the base is everything before the filename
// Imports can have arbitrary directory depth; BasePath is the directory path containing the file

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nested remote imports resolve against hardcoded .github/workflows/ instead of parent workflowspec base path

2 participants

Comments