Skip to content

gh issue artifact stack 8/10: Add edit - #14571

Open
BagToad wants to merge 3 commits into
bagtoad/artifact-createfrom
bagtoad/artifact-edit
Open

BagToad wants to merge 3 commits into
bagtoad/artifact-createfrom
bagtoad/artifact-edit

Conversation

@BagToad

@BagToad BagToad commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Part of the pull request stack tracked in #14563.

This adds gh issue artifact edit from #14526.

Description

Issue artifacts are Markdown documents and links attached to an issue. The previous pull requests added the gh issue artifact command group, list, view, delete, download and create. This one adds edit. It renames an artifact or replaces its content, and each edit saves a new version in the artifact's edit history:

$ gh issue artifact edit 142 2 --name 'OAuth callback plan v2'
✓ Updated artifact 2 (OAuth callback plan v2) on monalisa/monas-cafe#142
  • --name renames the artifact. --body or --body-file replaces its content, and --body-file - reads standard input. A file never renames the artifact.
  • The type never changes. A link's new content must be an http(s) URL, either from a .url Internet Shortcut file or as the whole content, with surrounding whitespace trimmed. A document never takes a .url file.
  • --attach uploads images and videos and points the new content's references at them, as gh issue edit does. On its own, it appends them to the current content, like gh issue comment --edit-last --attach. A link can't take --attach.
  • Types gh doesn't know are refused, as in view.
  • Piped, the artifact gets one line with the same five columns as create, delete and download, with the status updated. The source column is the --body-file path, or - for standard input.

Flags and the new content are checked before any request, so a missing, binary or empty file fails without one. The checks that depend on the type run after the artifact's lookup, before anything is uploaded or saved.

The API client gains Update. It sends only the fields that change, so --name on its own leaves the content alone. create's --attach uploader moves to the shared package as NewUploader, unchanged, so both commands build it the same way.

How did you test this change?

I recorded the built gh editing two artifacts on one issue in bagtoad/issue-artifacts-demo, checking each edit with gh issue artifact view, whose edit history grows by one version with each save: a rename, new content from a file and from standard input, a link's new URL from --body and from a .url file, --attach replacing a reference and appending an image on its own, the three type errors, nothing to change, and piped output. It leaves out failed uploads and saves, a type gh doesn't know, pull requests and GitHub Enterprise Server.

recording.mp4

Open chapters and agent notes

Key points

  • A link's new content is checked before anything is saved. newBody works out what to save from the artifact's type. For a link, a .url path is read as a shortcut, and anything else must be a bare URL:

    if shared.IsShortcutFile(opts.BodyFile) {
    	// A .url file gives the URL on its URL= line
    	u, err := shared.ShortcutURL(*content)
    	...
    }
    // Anything else, from --body, a file or standard input, must be the URL itself
    u, ok := shared.HTTPURL(*content)
    if !ok {
    	return nil, fmt.Errorf("artifact %d is a link; its new content must be an http(s) URL or a .url file", a.Number)
    }

    HTTPURL is the strict check view and download already use, so edit can't save a link that download would refuse to write as a shortcut.

  • --attach on its own starts from the current content. With no --body or --body-file, the files are attached to the body the artifact's lookup returned, and references in it resolve against the working directory.

  • An upload can't be undone, so an artifact is saved if any of its files uploaded. This is the --attach rule create follows for each document:

    md, uploadResult, err := uploader.UploadAndAttach(context.Background(), *body, dir, opts.Assets)
    // Nothing uploaded, so nothing is lost by not saving
    if err != nil && uploadResult.Uploaded == 0 {
    	shared.PrintFailure(opts.IO, result, action, err)
    	return cmdutil.SilentError
    }
    // Otherwise the save goes ahead, and the upload error is the reason on its line
    body, uploadErr = &md, err

    An artifact saved with only some of its files keeps the status updated, with the upload error as its reason, through the same line as create. In a terminal, gh prints this on stderr and exits 1:

    ! Updated artifact 2 (OAuth callback plan) on monalisa/monas-cafe#142, but could not upload ./latte-art.png: rate limited; wait and try again
    
  • A failed lookup or save is a result line. An artifact that doesn't exist gives X Failed to update artifact 9: Not Found, like delete, and piped, a failed line. Mistakes in what was asked stay plain errors, as [Design] gh issue artifact edit #14526 shows for the type errors, and so does a type gh doesn't know, as in view.

  • No artifact URLs yet. [Design] gh issue artifact edit #14526 also names an artifact by its URL. The API doesn't return artifact URLs yet, as discussed in RFC: gh issue artifact #14529, so edit takes an issue and a number, and its help leaves URLs out. A later pull request in this stack will add them.

  • --attach records the same telemetry event as the other --attach commands.

Notes for reviewers

Start with the edit run function, then newBody after it. TestEditRun runs each row in a temporary directory, with the client mock for artifact requests and HTTP stubs for uploads. It has rows for failed and partial uploads, failed lookups and saves, and a type gh doesn't know, which the recording can't show.

These messages aren't in #14526:

  • --name cannot be blank and --body cannot be blank, as in create.
  • failed to update artifact 2 from missing.md: no such file or directory, and the same form for a binary or empty file and for standard input.
  • broken.url: no [InternetShortcut] section; pass the URL with `--body` instead, for a link's .url file that isn't a shortcut. The other .url errors match create's.
  • X Failed to update artifact 9: Not Found, for an artifact that doesn't exist.
  • The line above for an artifact saved with only some of its files.

Related issues:

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.

Update saves a new version of one artifact. Its PATCH request sends only
the name or body it changes, since the API keeps any field a request
leaves out. An artifact's type can't change.
gh issue artifact edit needs the uploader create builds: the one issue
lookup that refuses a pull request and returns the repository ID and
permission an upload needs, then the checks attachments.NewUploader
makes. shared.NewUploader now builds it for both.
edit renames an artifact with --name, replaces its content with --body
or --body-file, or both, and saves a new version. --body-file - is the
only way standard input is read, and a file never renames the artifact.

The type never changes, so it is enforced before anything is uploaded or
saved. A link's new content must be an http(s) URL: a .url file is read
as an Internet Shortcut, and anything else must be a bare URL, with its
surrounding whitespace trimmed. A document never takes a .url file, and
a link can't take --attach. Types gh doesn't know are refused.

--attach on its own appends the files to the current body, like
gh issue comment --edit-last --attach. With new content, it points the
content's references at the uploads. The artifact is saved when any file
uploaded, since uploads can't be undone, and the upload error is the
reason on its line.

edit reports its artifact on one result line in the format create,
delete and download share. A failed lookup or save is a failed line,
like in delete.
@BagToad
BagToad requested a review from a team as a code owner October 1, 2026 16:59
@BagToad
BagToad requested review from niik and removed request for a team October 1, 2026 16:59
@BagToad
BagToad added this pull request to stack #14573 October 1, 2026 16:59

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.

1 participant