Skip to content

fix: gh aw add resolves uses: references transitively in imported files#44763

Merged
pelikhan merged 6 commits into
mainfrom
copilot/fix-gh-aw-add-uses-resolution
Jul 10, 2026
Merged

fix: gh aw add resolves uses: references transitively in imported files#44763
pelikhan merged 6 commits into
mainfrom
copilot/fix-gh-aw-add-uses-resolution

Conversation

Copilot AI commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

gh aw add correctly fetched direct imports: dependencies but silently dropped transitive ones — files referenced by those shared files were never installed.

Root Causes

Two bugs in fetchFrontmatterImportsRecursive (pkg/cli/includes.go):

1. Wrong base directory for bare filenames

Simple filenames with no / (e.g., control-aux.md) were resolved against originalBaseDir (the workflow root) instead of currentBaseDir (the importing file's directory). This mismatched the compiler's determineNestedBaseDir logic:

shared/control.md imports: [control-aux.md]

Before: resolves to .github/workflows/control-aux.md   ❌
After:  resolves to .github/workflows/shared/control-aux.md  ✓

2. Recursion skipped for pre-existing files

When force=false and a shared file already existed on disk, the code skipped both the download and recursion into that file's imports. Any transitive deps below an already-present file were never fetched.

Changes

  • pkg/cli/includes.go

    • Bare filenames (no /) now resolve using currentBaseDir; paths containing / continue to use originalBaseDir — matching compiler behavior
    • Pre-existing files (skip-download path) now have their content read and recursed into before continue
    • Added downloadFn field to frontmatterImportsOpts (nil → parser.DownloadFileFromGitHub) to enable unit-testable mock injection, consistent with the dispatch-workflow pattern
  • pkg/cli/remote_workflow_test.go

    • TestFetchFrontmatterImportsRecursive_SiblingPathResolution: asserts bare-filename import resolves to the importing file's directory, not the workflow root
    • TestFetchFrontmatterImportsRecursive_RecurseIntoExistingFile: asserts transitive deps are fetched even when the parent shared file already exists on disk

Generated by 👨‍🍳 PR Sous Chef · 10.4 AIC · ⌖ 4.23 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 10, 2026 15:37
- Add downloadFn to frontmatterImportsOpts for test injection
- Fix sibling path resolution: bare filenames resolve to importing file's dir
- Fix recursion skip: recurse into existing files' imports on force=false
- Add unit tests for both bug fixes

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix gh aw add to resolve uses: references in imported files fix: gh aw add resolves uses: references transitively in imported files Jul 10, 2026
Copilot AI requested a review from pelikhan July 10, 2026 15:39
@pelikhan pelikhan marked this pull request as ready for review July 10, 2026 15:40
Copilot AI review requested due to automatic review settings July 10, 2026 15:40
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes gh aw add so it correctly fetches transitive frontmatter imports: dependencies and resolves certain relative import paths the same way the compiler does, preventing silently-missing shared files during installation.

Changes:

  • Adjusts frontmatter import path resolution so bare filenames resolve relative to the importing file’s directory, while paths containing / resolve relative to the original workflow base directory.
  • Ensures recursion into imports still happens when an imported file already exists locally (and force=false), so transitive dependencies are discovered and fetched.
  • Adds unit tests to validate sibling (bare-filename) resolution and recursion into pre-existing imported files, using an injectable download function for testability.
Show a summary per file
File Description
pkg/cli/includes.go Updates recursive frontmatter import fetching: path resolution, recurse-when-existing behavior, and injectable download function.
pkg/cli/remote_workflow_test.go Adds targeted tests covering sibling resolution and recursion into already-present imported files.

Review details

Tip

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

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread pkg/cli/includes.go Outdated
Comment on lines 302 to 306
// Fallback: if the selected baseDir is empty (e.g. workflow at repo root
// with no originalBaseDir set), use currentBaseDir as a last resort.
if baseDir == "" {
baseDir = currentBaseDir
}

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The fix is correct and the approach is sound. Two bugs fixed: (1) bare-filename resolution now uses currentBaseDir, matching the compiler logic; (2) pre-existing files are now recursed into for transitive deps. Cycle safety is intact via the shared seen map. Test coverage is solid with mock downloadFn injection.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 21.9 AIC · ⌖ 4.28 AIC · ⊞ 4.8K

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 80/100 — Acceptable

Analyzed 2 test(s): 2 design, 0 implementation, 0 violation(s).

📊 Metrics (2 tests)
Metric Value
Analyzed 2 (Go: 2, JS: 0)
✅ Design 2 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 2 (100%)
Duplicate clusters 0
Inflation Yes (2.79:1)
🚨 Violations 0
Test File Classification Issues
TestFetchFrontmatterImportsRecursive_SiblingPathResolution remote_workflow_test.go:693 Behavioral contract None
TestFetchFrontmatterImportsRecursive_RecurseIntoExistingFile remote_workflow_test.go:746 Behavioral contract Slight test inflation

Test Details

TestFetchFrontmatterImportsRecursive_SiblingPathResolution (lines 689-739)

  • Purpose: Verifies sibling imports (bare filenames) resolve relative to the importing file's directory
  • Coverage: Direct path resolution, file placement verification
  • Assertions: 5 (require.Len, assert.Equal, 2× assert.FileExists, assert.NoFileExists)
  • Mocking: Stub downloader to track resolved paths (acceptable external I/O mock)
  • Verdict: ✅ High-value behavioral test

TestFetchFrontmatterImportsRecursive_RecurseIntoExistingFile (lines 741-807)

  • Purpose: Verifies transitive dependencies are fetched even when parent import exists locally (force=false path)
  • Coverage: Existing file skipping, recursive traversal, sibling resolution in transitive context
  • Assertions: 5 (assert.NotContains, require.Len, assert.Equal, assert.FileExists, assert.NoFileExists)
  • Mocking: Stub downloader with download tracking
  • Verdict: ✅ High-value behavioral test covering complex edge case

Verdict

Passed. 0% implementation tests (threshold: 30%). Both tests enforce design invariants: the fix's core logic for transitive imports with sibling path resolution. Slight test inflation (120 test lines : 43 prod lines = 2.79:1) is acceptable for behavioral coverage of non-trivial import logic.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 14.7 AIC · ⌖ 20.2 AIC · ⊞ 6.8K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 80/100. 0% implementation tests (threshold: 30%). Both tests are high-value behavioral contracts enforcing the core fix logic for transitive imports with sibling path resolution.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — COMMENT with minor suggestions.

📋 Key Themes & Highlights

Key Themes

  • Silent coupling to compiler logic: The strings.Contains(filePath, "/") heuristic mirrors determineNestedBaseDir but is not linked to it by comment. Drift between the two will be invisible until it breaks.
  • Recursion base-dir clarity: The importedBaseDir := path.Dir(remoteFilePath) choice in the pre-existing-file branch is correct but subtly relies on remoteFilePath being the canonical remote path — worth a comment.
  • Test completeness: Tests verify file existence and download paths, but not the written file content.

Positive Highlights

  • ✅ Both bugs are properly diagnosed, root-caused, and fixed — not just papered over
  • downloadFn injection follows the established dispatch-workflow pattern, keeping tests network-free
  • ✅ Test names read as specifications; Arrange/Act/Assert structure is clear
  • ✅ The seen map prevents infinite cycles through already-visited imports
  • ✅ Detailed PR description makes the root-cause analysis easy to follow

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 38.4 AIC · ⌖ 4.64 AIC · ⊞ 6.6K
Comment /matt to run again

Comment thread pkg/cli/includes.go
// Example: "shared/reporting.md" from ".github/workflows/shared/daily-audit-base.md"
// → ".github/workflows/shared/reporting.md"
var baseDir string
if !strings.Contains(filePath, "/") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] The strings.Contains(filePath, "/") heuristic mirrors determineNestedBaseDir in the compiler, but no code comment links to that function. If the compiler logic diverges, this silent coupling will be hard to spot.

💡 Suggestion

Add a reference comment above the if block:

// Resolution mirrors pkg/parser.determineNestedBaseDir:
// bare filenames (no "/") → sibling of the importing file (currentBaseDir)
// slash-paths              → repo-root-relative (originalBaseDir)
if !strings.Contains(filePath, "/") {

This makes it easy to grep for the coupling and keep both sides in sync.

@copilot please address this.

Comment thread pkg/cli/includes.go
// any missing transitive dependencies.
if existingContent, readErr := os.ReadFile(targetPath); readErr == nil {
importedBaseDir := path.Dir(remoteFilePath)
fetchFrontmatterImportsRecursive(ctx, string(existingContent), importedBaseDir, opts)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] When recursing into an existing file, importedBaseDir is set to path.Dir(remoteFilePath), but remoteFilePath may equal originalBaseDir + "/" + localRelPath — not the local disk path. This is correct for remote-path recursion, but if the existing file's local content references paths that were written relative to a different layout, the recursion will use the wrong base dir.

Consider adding an assertion or comment that remoteFilePath is always the canonical remote path at this point, so future readers know this is intentional and not a bug.

@copilot please address this.

}

// Process shared/control.md; currentBaseDir mirrors its location in the source repo.
fetchFrontmatterImportsRecursive(t.Context(), controlContent, ".github/workflows/shared", opts)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The test asserts require.Len(t, downloadedPaths, 1) but does not verify the content or location of the written file for the stub ([]byte("# stub\n")). The file-existence check later catches gross path errors, but a test that also asserts the written bytes would make the regression test more complete and protect against silent overwrites.

💡 Example addition
content, err := os.ReadFile(filepath.Join(tmpDir, "shared", "control-aux.md"))
require.NoError(t, err)
assert.Equal(t, "# stub\n", string(content))

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: non-blocking observations

The two fixes (sibling-path resolution and recursion into existing files) are correct and well-tested. Two issues worth addressing before this hardens further:

Issues found

1. Silent transitive-dep failures (medium)fetchFrontmatterImportsRecursive is void and swallows all internal errors. A failed transitive download or write produces an incomplete local install with no signal to the user beyond a log line. Addressed in the inline comment at line 384.

2. Windows path-separator heuristic (low)strings.Contains(filePath, "/") uses a hard-coded POSIX separator. Addressed in the inline comment at line 297.

🔎 Code quality review by PR Code Quality Reviewer · 48.4 AIC · ⌖ 4.53 AIC · ⊞ 5.4K
Comment /review to run again

Comment thread pkg/cli/includes.go
// any missing transitive dependencies.
if existingContent, readErr := os.ReadFile(targetPath); readErr == nil {
importedBaseDir := path.Dir(remoteFilePath)
fetchFrontmatterImportsRecursive(ctx, string(existingContent), importedBaseDir, opts)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Silent failure on transitive dependency recursion: errors inside the recursive call are fully swallowed — if any transitive download or write fails, the caller has no indication that the local copy is incomplete.

💡 Detail

At line 384, fetchFrontmatterImportsRecursive is called for an existing file's imports. The function has no return value, so download failures, disk-write failures, or permission errors inside recursion are silently logged at best. A broken transitive dep leaves an incomplete install with no observable error surfaced to the user.

The same void-return pattern exists at line 438 (newly downloaded files), so partial-failure state cannot propagate at any recursion depth.

Consider returning an error or accumulating failures (e.g., a []error slice) so the top-level caller can report incomplete installs clearly.

Comment thread pkg/cli/includes.go
// Example: "shared/reporting.md" from ".github/workflows/shared/daily-audit-base.md"
// → ".github/workflows/shared/reporting.md"
var baseDir string
if !strings.Contains(filePath, "/") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Forward-slash heuristic breaks on Windows paths: strings.Contains(filePath, "/") only detects POSIX separators; on Windows, import paths using backslashes will be misclassified as bare filenames and resolved against currentBaseDir instead of originalBaseDir.

💡 Suggested fix

Normalise filePath to forward-slashes before the check, consistent with the filepath.FromSlash usage elsewhere in this function:

normalisedFilePath := filepath.ToSlash(filePath)
if !strings.Contains(normalisedFilePath, "/") {
    baseDir = currentBaseDir
} else {
    baseDir = opts.originalBaseDir
}

Or use path.Base(filePath) == filePath as a cross-platform equivalent (true when there is no directory component).

…tion

Documents the path-structure heuristic and recursive-into-existing-file
decisions made in pkg/cli/includes.go as part of PR #44763.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (163 new lines in pkg/cli/) but does not have a linked Architecture Decision Record (ADR).

Draft ADR committed: docs/adr/44763-resolve-transitive-uses-imports-by-path-structure.md — review and complete it before merging.

This PR cannot merge until an ADR is linked in the PR body.

What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list any additional alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-44763: Resolve Transitive Uses Imports Using Path Structure Heuristic

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I will deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 54.1 AIC · ⌖ 13.1 AIC · ⊞ 8.5K ·
Comment /review to run again

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

Generated by 👨‍🍳 PR Sous Chef · 10.4 AIC · ⌖ 4.23 AIC · ⊞ 7.1K ·
Comment /souschef to run again

When originalBaseDir is empty (workflow at repo root) and a shared file
imports a path with "/" (e.g. "shared/helper.md"), the previous fallback
incorrectly prefixed the path with currentBaseDir, producing
"shared/shared/helper.md" instead of "shared/helper.md".

Remove the fallback entirely; the existing `if baseDir != ""` block
already handles the empty case by using filePath as-is.

Add TestFetchFrontmatterImportsRecursive_RepoRootSlashPath to cover this
scenario explicitly.

Also update wasm golden files for the oauth_token_check_failed field
added in the recent main merge.

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

...

Addressed the review feedback in the latest commit. The baseDir == "" fallback block has been removed — it incorrectly overrode originalBaseDir's intentional empty value for slash-containing paths, causing shared/shared/helper.md instead of shared/helper.md for repo-root workflows. The existing if baseDir != "" { … } else { remoteFilePath = filePath } already handled this correctly. Added TestFetchFrontmatterImportsRecursive_RepoRootSlashPath to cover the scenario. Also updated wasm golden files for the oauth_token_check_failed field added during the recent main merge.

Copilot AI requested a review from gh-aw-bot July 10, 2026 16:23
@pelikhan pelikhan merged commit 30d7e14 into main Jul 10, 2026
29 checks passed
@pelikhan pelikhan deleted the copilot/fix-gh-aw-add-uses-resolution branch July 10, 2026 16:30
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.8

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.

gh aw add does not resolve uses: references in imported files

4 participants