Skip to content

Pass through to gh with a -- separator, delegating to gh --attach when it can do the job - #61

Draft
drogers0 wants to merge 3 commits into
mainfrom
issue-58-passthrough
Draft

Pass through to gh with a -- separator, delegating to gh --attach when it can do the job#61
drogers0 wants to merge 3 commits into
mainfrom
issue-58-passthrough

Conversation

@drogers0

@drogers0 drogers0 commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Implements the passthrough proposed in #58: everything left of -- is ours, everything right of it is a gh invocation.

gh image diagram.pdf -- pr create --title "Design" --body-file design.md
gh image shot.png    -- issue comment 42 --body-file note.md
gh image shot.png    -- pr create --fill

What happens, in plain terms

Two routes exist. You never pick one — the tool works out which can do the job. Either way you type one command and the file ends up in the body.

What you run What happens Why
A PNG or MP4 on create, with --body/--body-file gh-image uploads, rewrites the body, hands the finished body to gh On a create there is nothing to inspect afterwards, so we never risk a second PR
A PNG or MP4 on create, without a body flag (--fill, editor) Handed straight to gh --attach --fill means gh writes the body, so there is nothing for us to rewrite
A PDF, zip, log or any other type, on comment/edit gh is tried first, refuses the type, gh-image takes over Upstream only takes 9 image and video extensions
Any file on a repo you only have read access to, on comment/edit gh is tried first, refuses, gh-image takes over Upstream needs write access; our route does not
Any file, on a gh too old to have --attach gh-image does the whole job We check the actual gh on your machine, per command
A file on create with no body flag and no --attach support Refused before anything uploads Nothing could carry the reference, so nothing is wasted

Where the file lands in the body:

Your body Result
Mentions the file — ![shot](./shot.png) That reference is rewritten to the uploaded URL, in place
Does not mention it The reference is appended at the end

Path spelling does not matter: ./shot.png, shot.png and docs/../shot.png all match the same file, the way gh matches it.

One case stops rather than guessing: if gh fails partway through a comment but has already posted it, we do not post a second one — we stop and name the files that may be missing.

Routing, precisely

  • Probe. gh <subcommand> --help is checked for --attach once per run — per command, not per binary, so a build carrying the flag on only some commands routes correctly. gh resolves via GH_PATH, then PATH.
  • create — delegation and fallback never mix, so the double-create hazard from upstream's partial-failure behaviour (UploadAndAttach writes when at least one asset uploaded) cannot arise: body flag → our route with no --attach; no body flag → delegate verbatim and never fall back; no body flag and no --attach support → refuse before uploading.
  • comment — delegate first; on a non-zero exit check the target for a comment by the viewer since launch (with a clock-skew allowance). Something posted → stop and report. Nothing posted → our route.
  • edit — delegate first; on failure fall back without a verification query, since re-execing an edit overwrites the body deterministically.

This adopts the refinement discussion on the issue with one change to the second comment's rule: create delegates exactly when fallback is impossible (no body flag), rather than never. Every upstream refusal class — extension, size, token type, permission, host — exits before any side effect, verified against cli/cli trunk, so -- pr create --fill with an eligible file keeps working instead of being refused.

Fallback rewriter (internal/passthrough)

goldmark walk of inline image and link nodes. Destinations naming an uploaded file are replaced by byte range, preserving titles and angle-bracket wrappers, skipping code spans and fences. Both sides resolve to an absolute path before matching, as upstream's rewriter does. Reference-style links error with a request for an inline link. Files the body never references are appended using the existing per-type markdown — image embed, bare video URL, [name](url) otherwise. About 280 lines against upstream's ~810, which is an internal package and not importable.

Only upstream surface knowledge taken on: the six subcommands plus their cobra aliases (pr new, issue new), the four body-flag spellings, --repo/-R for targeting the fallback upload, and --title (whose value can begin with a dash). User-defined gh aliases are refused with a clear message rather than guessed at, as is --attach right of --, which would otherwise be honoured on one route and dropped on the other.

Behaviour notes

  • stdout carries what gh prints, with gh's exit code; route notes and diagnostics go to stderr. The no--- mode is unchanged.
  • --body-file - is buffered before delegation so the fallback can replay it.
  • Breaking: gh image -- -dashed-file.png (escape-dash) is gone — -- now always means passthrough. Use gh image ./-dashed-file.png.

Testing

Probe, exec and comment-verification are injected boundaries alongside the existing newUploader/newDownloader, so both routes are exercised with no live gh and no network: routing per kind × probe × body-flag, stdin replay across delegate-then-fallback, argv building, and table-driven rewriter cases covering inline, titled, angle-bracket and escaped destinations, path spellings, code spans and fences, and per-type appends.

gofmt, go vet, go build, go test ./... all clean. The lint job is pinned to the module's Go version in a separate commit — golangci-lint v2.12.2 cannot typecheck the Go 1.27 standard library, which fails main today as well.

Closes #58

@drogers0
drogers0 marked this pull request as draft September 1, 2026 22:59
…sition

Three defects found reviewing #61, each silent:

- A body reference spelled differently from the command line never
  matched, so ./shot.png with a body naming shot.png left the reference
  dangling and appended a duplicate. Upstream resolves both sides to an
  absolute path; now so do we, keeping the raw destination bytes for the
  replacement so escaped and angle-bracket spellings still work.
- Rebuilding the argv stripped any token starting with -b, -F, --body or
  --attach, so a value like `--title -Fix crash` lost its value and left
  --title to swallow --body. Classify now records the positions its body
  flags occupy and only those are dropped. It also consumes --title's own
  value, the one upstream flag whose value plausibly starts with a dash.
- --attach right of -- was validated and then dropped on the gh-image
  route, losing a file without a word. It is refused instead, naming the
  files-left-of-- form that works on both routes.
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.

Pass through to gh with a "--" separator, delegating to gh --attach when it can do the job

1 participant