Adding ht-github-attachment-reupload to hermes-talaria — Copying GitHub Attachments Across Repos via the Authenticated Browser Tadashi Shigeoka added the ht-github-attachment-reupload skill to the hermes-talaria Hermes Agent repository via pull request codenote-net/hermes-talaria#56, letting an AI agent copy GitHub issue, PR, and discussion attachments between repositories. The skill restricts original-byte transfer to the authenticated browser UI, forbidding curl, wget, the gh attachment APIs, HTTP libraries, undocumented upload endpoints, and cookie or token exports, and requires destination-audience approval before uploading to public repos or cross-organization and cross-host destinations. It separates hash-based byte-integrity verification from destination-only permission verification and resumes interrupted runs against per-item state and evidence. Adding ht-github-attachment-reupload to hermes-talaria — Copying GitHub Attachments Across Repos via the Authenticated Browser Tadashi Shigeoka https://codenote.net/en/author/tadashi-shigeoka/ · Tue, October 6, 2026 Attachments on GitHub issues, PRs, and discussions are served against the permissions of the repository where they were posted. Re-posting the body text alone to another repository breaks the images and file downloads for anyone who does not also have access to the original repo. This bites when carrying records from an internal private repo over to a public repo, or to a repo owned by a different organization. I added a skill that hands this carry-over to an AI agent to hermes-talaria https://github.com/codenote-net/hermes-talaria . The PR is codenote-net/hermes-talaria 56 https://github.com/codenote-net/hermes-talaria/pull/56 . The skill name is ht-github-attachment-reupload , invoked as /ht-github-attachment-reupload . Original-byte transfer is scoped to the authenticated browser UI; curl , gh attachment APIs, and HTTP libraries are not permitted to move files. This post walks through the design decisions I made along the way. Overview of What the Skill Adds - A Hermes Agent https://hermes-agent.nousresearch.com/ skill that hands GitHub attachment reupload to an AI agent - Covers issue bodies/comments, PR bodies/conversation comments, and discussion bodies/top-level comments/replies, including cross-type copies - Transfers only through the authenticated browser UI; forbids file transfer via curl https://curl.se/ , the gh https://cli.github.com/ attachment APIs, HTTP libraries, and cookie/token exports - Requires destination-audience approval before upload, with explicit approval for public repos, cross-organization copies, and cross-host copies - Separates byte-integrity verification hash comparison from destination-only permission verification a session with destination access but no source access - On interruption, resumes against per-item state and evidence, assuming GitHub may have already uploaded files even when the comment is not yet submitted - Keeps downloaded originals, private URLs, signed URLs, screenshots, the ledger, and source text outside Git Scoping Original Transfer to the Authenticated Browser UI The decision I spent the most time on was which transport to use for the originals. A naive implementation would download from the source’s user-attachments signed URL with curl https://curl.se/ and upload to GitHub’s attachment endpoint as multipart/form-data , or go through gh https://cli.github.com/ ’s internal API. These are fast and automate well. I did not adopt either, for two reasons. First, attachment delivery is tied to posting-repo permissions, and if the agent carries the source’s signed URL into another environment, the agent effectively becomes a bridge that pours source-permissioned bytes into the destination. Second, GitHub’s attachment endpoint is not a public contract. Once an endpoint inferred from observation is wired into automation, the transfer can no longer be treated as “an upload the user approved through the UI.” So I restricted original transfer to the signed-in browser UI. SKILL.md states explicitly: - Download via the attachment link’s click, or Save Image As on the original image. A screenshot, a thumbnail, or Save Page As HTML is not the original - Upload by selecting the observed bytes through the destination editor’s file input or the native file chooser - Do not transfer files via curl , wget , gh attachment APIs, HTTP libraries, undocumented upload endpoints, or JavaScript fetch / XHR - Do not export cookies or tokens - Browser download/save APIs triggered by a page’s normal download action are allowed The specific browser product is not fixed. Agent Browser https://agent-browser.dev/ tools and Computer Use https://github.com/trycua/cua for native file choosers and context menus are used together, under observation. Fixed CSS selectors, coordinates, guessed endpoints, and command flags are not hard-coded, following the same observe-then-act design as ht-japan-hotel-research see Adding ht-japan-hotel-research to hermes-talaria https://codenote.net/en/posts/hermes-talaria-japan-hotel-research-skill/ . Native-Chooser Recovery Once I scoped transfer to the browser UI, the next awkward surface was GitHub’s native file chooser. Element-index input can fail with snapshot id required , and repeating the same call does not resolve it. The recovery routine lives in references/github-attachments.md https://github.com/codenote-net/hermes-talaria/blob/main/skills/ht-github-attachment-reupload/references/github-attachments.md . It proceeds in order: 1. Attempt recovery in the background first, re-reading the driver schema and reissuing with a fresh snapshot/token 2. A chooser visible in the parent AX tree is not necessarily addressable through that parent, so element outside target window and unresolved-window refusals are respected 3. Only after a verified background failure does the agent escalate: it asks for explicit approval to keep the target browser and chooser in front. Approval is for a brief foreground action, not persistent focus hijack or Space switching 4. When the driver recommends desktop recovery, capture a fresh get desktop state image and use its PNG pixel coordinates against kind: desktop , display id: primary . AX desktop bounds, window-local images, and resized display images are not mixed 5. After selection, read the filename back and verify size/hash where exposed During implementation, a direct cua-driver call via the desktop-coordinate recovery path selected a harmless downloaded TXT once, under explicit foreground approval. Wrapper-only, background-only end-to-end automation is left as “not verified” in the PR description. Recording manual-selection paths separately from automated ones is an operational line to prevent “one successful native selection” from being read as “the whole flow is automated.” Destination-Audience Approval Before Upload The guard the skill prioritizes above all others is destination-audience approval before upload. Permission to read the source does not amount to approval to publish the same content to the destination’s audience. SKILL.md requires: - Explicit approval when the destination is a public repo, when the copy crosses organizations, when it crosses hosts GitHub Enterprise Server and GitHub.com , or when the audience is unknown - Approval is obtained before upload, not just before submission. GitHub can upload files during the draft phase before the submit button is pressed, so a guard that only runs at submission is too late - Reading access to the source does not count as approval for wider disclosure - Source URLs stay out of the destination text unless they are explicitly approved The mode new comment / reply vs. edit of existing content is part of what gets authorized. When ambiguous, the skill asks; it never substitutes a new comment for an edit the user cannot perform. When editing existing content, it re-reads the current content right before saving, and if another author has changed it, it stops and reconciles instead of overwriting. Separating Byte Integrity from Destination-Only Permission Determining whether the reupload succeeded is also not folded into a single check. Byte integrity and destination-side permission are verified separately, and the permission result is recorded on its own axis, apart from the per-item states described later. Byte-integrity verification compares size and SHA-256 between the downloaded original and the copy re-downloaded through the browser from the destination after reupload the file name appears in inspect output but is not compared . The helper’s implementation and the files it rejects are covered in the Local Integrity Helper section below. What the integrity helper guarantees is “the bytes match.” It does not guarantee that GitHub authorizes delivery correctly, or that the destination’s audience can actually view the attachment. Destination-only permission verification is a separate step: with a session that has access to the destination but not to the source, confirm image rendering and general file download. A session with access to both, or an incognito session that also cannot reach the destination, does not meet this requirement. How the report phrases this is covered below. php flowchart LR S "Source repo attachment" -- |"Download via browser"| L "Local original