Skip to content

gh issue artifact stack 1/11: Resolve --attach references against the body file's directory - #14564

Open
BagToad wants to merge 1 commit into
trunkfrom
bagtoad/artifact-attach-relative-paths
Open

BagToad wants to merge 1 commit into
trunkfrom
bagtoad/artifact-attach-relative-paths

Conversation

@BagToad

@BagToad BagToad commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Part of the pull request stack tracked in #14563.

This applies the reference rule from the gh issue artifact create design in #14525 to the six commands that already have --attach.

Description

--attach uploads a local image or video. If the body already references the file, gh replaces that reference with the uploaded URL. Otherwise it appends the file to the body.

To match a reference to an attached file, gh compares absolute paths. Until now, every reference resolved against the working directory. That breaks a common layout: a Markdown file kept beside its images.

docs/plan.md           contains ![Sign-in flow](./signin-flow.png)
docs/signin-flow.png

Running gh issue create --body-file docs/plan.md --attach docs/signin-flow.png from the project root looked for ./signin-flow.png in the root. Nothing matched, so gh left the local path in the body and appended the image at the end.

A reference in a --body-file now resolves against that file's directory first. If that matches no attachment, it falls back to the working directory, so references that matched before still match. --body and standard input have no file, so they resolve against the working directory as before.

The rule lives in the shared --attach code, so gh issue create, gh issue edit, gh issue comment, gh pr create, gh pr edit and gh pr comment all follow it. Each command's help gains one sentence: "Paths in a --body-file are relative to its directory, falling back to the working directory." The gh agent skill in this repository describes the same rule.

How did you test this change?

I recorded all six commands against bagtoad/issue-artifacts-demo and read each stored body back with gh. The video also covers the working-directory fallback, standard input and two attached files that share a name, but not absolute paths or Windows.

recording.mp4

Open chapters and agent notes

Key points

Each way of reading a reference (as written, unescaped, percent-decoded) is tried beside the body file first, then in the working directory:

spellings := candidatePaths(dest)
paths := make([]string, 0, 2*len(spellings))
if markdownDir != "" {
	for _, s := range spellings {
		// Rooted and drive-relative Windows paths are never moved into the folder
		if isRelativePath(s) {
			paths = append(paths, filepath.Join(markdownDir, s))
		}
	}
}
// The working directory comes second, so earlier matches still work
paths = append(paths, spellings...)
  • The file beside the body file wins. If signin-flow.png and docs/signin-flow.png are both attached, ./signin-flow.png in docs/plan.md now takes the one in docs/ and the other is appended. Before, it took the one in the working directory. This only matters when a reference could name an attached file in either place.

  • A reference that now matches follows the existing rules for matched references. So a video written as a reference-style image beside the body file is now refused before anything uploads, and the command exits 1. Before, that reference matched nothing, so gh appended the video, left the local path in the body and exited 0.

  • The directory is a required parameter of UploadAndAttach, not an optional setting. Every caller has to say where its Markdown came from, and "" means it came from no file. A new --attach command can't forget it.

  • --body-file - sets no directory, since standard input has no folder:

    opts.Body = string(b)
    // Standard input keeps the working directory
    if bodyFile != "-" {
    	opts.BodyDir = filepath.Dir(bodyFile)
    }
  • Only --body-file sets a directory. A template's folder is never used, because template text is a starting point the user edits.

  • The directory is the one in the path as typed. A symlinked body file resolves beside the link, not its target.

Notes for reviewers

Start with the lookup in the attachments package's attachmentArgForDestination, then the new parameter on UploadAndAttach. The six commands only record the folder and pass it on, as in gh issue create. gh issue comment and gh pr comment share the upload in the commentable code.

  • The "File in another directory" and "Working directory fallback" scenarios in [Design] gh issue artifact create #14525 show this rule for artifact documents, which later pull requests in this stack build on.

Authorship and follow-up

Who wrote this:

  • A human wrote it.
  • An agent wrote it under close human direction.
  • An agent wrote it independently, and no human has guided the implementation beyond the initial prompt.

Who answers review comments:

  • @BagToad will read and reply directly. Name the account.
  • An agent will draft replies and @BagToad will read them before they are posted.
  • Nobody has explicitly committed to replying.

A Markdown file's relative links are written relative to the file, but a
--body-file had to reference attached images relative to the working
directory instead. References in a --body-file now resolve against the
file's directory first and the working directory second, so references
that matched before still match. --body and standard input still resolve
against the working directory.

The directory is a parameter of UploadAndAttach, so all six --attach
commands resolve references the same way.
@BagToad
BagToad requested a review from a team as a code owner October 1, 2026 16:58
@BagToad
BagToad requested review from babakks and a balanced review from Copilot October 1, 2026 16:58
@BagToad
BagToad added this pull request to stack #14573 October 1, 2026 16:59

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.

Copilot review overview

🟡 Changes recommended

The Windows drive-relative path branch lacks direct cross-platform regression coverage.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

Updates shared attachment handling so Markdown references from --body-file resolve relative to that file before falling back to the working directory.

Changes:

  • Passes body-file directories through all six --attach commands.
  • Adds directory-aware attachment matching and tests.
  • Updates command help and the gh skill documentation.
File Description
skills/​gh/​SKILL.md Documents reference resolution.
pkg/​cmd/​pr/​shared/​commentable.go Passes body directories for comments.
pkg/​cmd/​pr/​shared/​commentable_test.go Tests comment reference rewriting.
pkg/​cmd/​pr/​edit/​edit.go Adds body-directory handling.
pkg/​cmd/​pr/​edit/​edit_test.go Tests PR edit behavior.
pkg/​cmd/​pr/​create/​create.go Adds body-directory handling.
pkg/​cmd/​pr/​create/​create_test.go Tests PR creation behavior.
pkg/​cmd/​pr/​comment/​comment.go Captures the body-file directory.
pkg/​cmd/​pr/​comment/​comment_test.go Tests directory capture.
pkg/​cmd/​issue/​edit/​edit.go Adds body-directory handling.
pkg/​cmd/​issue/​edit/​edit_test.go Tests issue edit behavior.
pkg/​cmd/​issue/​create/​create.go Adds body-directory handling.
pkg/​cmd/​issue/​create/​create_test.go Tests issue creation behavior.
pkg/​cmd/​issue/​comment/​comment.go Captures the body-file directory.
pkg/​cmd/​issue/​comment/​comment_test.go Tests directory capture.
internal/​attachments/​test.go Supports nested test assets.
internal/​attachments/​references.go Implements directory-first matching.
internal/​attachments/​references_test.go Covers reference-resolution scenarios.
internal/​attachments/​references_fixture_test.go Updates the helper signature.
internal/​attachments/​doc.go Documents the new parameter.
internal/​attachments/​attach.go Propagates Markdown directories.
internal/​attachments/​attach_test.go Tests uploader integration.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +397 to +400
if path == "" || filepath.IsAbs(path) || filepath.VolumeName(path) != "" {
return false
}
return !os.IsPathSeparator(path[0])
@BagToad BagToad changed the title gh issue artifact stack 1/10: Resolve --attach references against the body file's directory gh issue artifact stack 1/11: Resolve --attach references against the body file's directory Oct 2, 2026

This branch has not been deployed

No deployments
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.

2 participants