Skip to content

Follow-up to #266: make the pull request template actually reach GitHub - #272

Merged
schpetbot merged 1 commit into
mainfrom
oss-pr-lander/266
Sep 1, 2026
Merged

Follow-up to #266: make the pull request template actually reach GitHub#272
schpetbot merged 1 commit into
mainfrom
oss-pr-lander/266

Conversation

@schpetbot

Copy link
Copy Markdown
Collaborator

Follow-up to #266. Draft until #266 lands — this branch currently contains @maparent's commit too, and will be rebased down to just the delta once #266 is merged.

#266 adds -T/--template and a pr_template config option. The surface is right and is kept exactly as designed — same flag letter, same config key, same precedence. The mechanism can't work, though, and I couldn't find a variant of it that does.

The feature currently fails on every invocation

issue pr already passes --body <issue url>, and #266 appends --template beside it. gh refuses that pair:

$ linear issue pr CLI-113 -T tmpl.md
`--template` is not supported when using `--body` or `--body-file`
✗ Failed to create pull request

Worse, pr_template in config applies unconditionally, so setting it breaks issue pr entirely rather than only when the flag is passed.

Why the obvious repair is worse

Dropping --body to make room for --template looks right, but gh only consults a template when it's running interactively. Without a body, a non-TTY caller gets:

must provide `--title` and `--body` (or `--fill` ...) when not running interactively

…and no pull request at all. That trades a broken flag for a command broken in CI, scripts, and agents. Handing gh a temp file that already contains the template fails identically — the problem is the missing --body, not the file's contents.

What this does instead

Read the template here and fold it into the body we already send, with the issue URL appended:

## Summary

<!-- what changed -->

## Testing

https://linear.app/schpet/issue/CLI-113/...

The URL has to survive — it's what Linear matches on to attach the PR to its issue. Putting it last leaves the template's prose as the first thing a reviewer reads. No --template flag ever reaches gh, so every existing flag (--web, --draft, --base, --head, --title) keeps working unchanged.

Also fixed

  • Config-relative paths broke outside the repo root. The loader finds <repo-root>/.linear.toml from any subdirectory, but its relative value was resolved against the cwd — so the documented pr_template = ".github/pull_request_template.md" worked at the root and failed everywhere below it. A config value now resolves against the config file that supplied it; a path typed on the command line stays cwd-relative, which is what a shell user expects.
  • An invalid configured value was silently ignored. getOption() drops values that fail to parse, so pr_template = 123 would quietly produce a URL-only PR. Now a ValidationError, following the existing resolveIssueSort precedent — per CLAUDE.md, explicit input works or errors.
  • Unusable templates fail loudly, naming the path: missing, directory, non-regular, unreadable. Silently falling back would ship a PR missing the content the user asked for.
  • NUL bytes are rejected. Deno.readTextFile doesn't refuse binary input — it substitutes U+FFFD and keeps NULs, which Deno.Command then rejects with a bare TypeError that never names the file.

One deliberate surface change

#266's description suggests -T "" to override a configured default. That worked only because an empty string is falsy. It's now an explicit --no-template flag; -T "" gets cliffy's Missing value for option "--template", and --template x --no-template is rejected before anything runs.

Verification

573 tests pass; deno task check, deno lint, deno fmt --check clean. New test/commands/issue/issue-pull-request.test.ts covers body composition and every template-read rejection; test/config.test.ts covers strict validation and config-relative resolution.

QA'd against a gh shim capturing real argv: config default, --no-template, --web + template, missing/directory/binary/empty template, --template + --no-template, and a relative config path from a nested subdirectory — confirming the composed body and that --template never reaches gh.

Generated skill docs under skills/ are untouched; they're produced from an installed binary out of band and are already stale on trunk (they don't list --id from #268 either). CI only verifies they generate, not that they're current.

@linear

linear Bot commented Sep 1, 2026

Copy link
Copy Markdown

CLI-113

#266 adds `-T/--template` and a `pr_template` config option, which are the right
surface -- the names and the precedence are kept exactly as the contributor
designed them. The mechanism cannot work, though, and I could not find a variant
of it that does.

The command already passes `--body <issue url>`, and #266 appends `--template`
next to it. `gh` refuses that pair outright:

    `--template` is not supported when using `--body` or `--body-file`

So every use of the new flag fails, and setting `pr_template` in config breaks
`issue pr` on every invocation rather than only when the flag is passed.

Dropping `--body` to make room for `--template` -- the obvious repair -- is
worse. `gh` only consults a template when it is running interactively; without a
body a non-TTY caller gets

    must provide `--title` and `--body` (or `--fill` ...) when not running interactively

and no pull request at all. That would trade a broken flag for a command broken
in CI, scripts, and agents. Handing `gh` a temporary file that already contains
the template fails the same way, because the problem is the missing `--body`,
not the file's contents.

So the template is read here and folded into the body we already send, with the
issue URL appended after it. The URL is what Linear matches on to attach the pull
request to its issue, so it has to survive; putting it last leaves the template's
prose as the first thing a reviewer reads. Every existing flag keeps working,
because the argv shape is unchanged.

Reading the file ourselves means we own its failures, and per CLAUDE.md an
explicitly requested template that cannot be used is an error rather than a
silent fallback to a URL-only body -- otherwise the user gets a pull request
quietly missing the content they asked for. Missing paths, directories,
non-regular files and unreadable files all produce a message naming the path.
NUL bytes are rejected too: `Deno.readTextFile` does not refuse binary input, it
substitutes U+FFFD and keeps the NULs, which `Deno.Command` then rejects with a
bare TypeError that never mentions the file.

One deliberate surface change: #266's description suggests `-T ""` to override a
configured default. That worked only because an empty string happened to be
falsy. It is now an explicit `--no-template` flag, and `-T ""` errors with a
suggestion pointing at it.

The generated skill docs under skills/ are left alone; they are produced from an
installed binary out of band and are already stale on trunk.
@schpetbot
schpetbot marked this pull request as ready for review September 1, 2026 00:27
@schpetbot
schpetbot merged commit 9058328 into main Sep 1, 2026
15 checks passed
@schpetbot
schpetbot deleted the oss-pr-lander/266 branch September 1, 2026 00:29
@schpetbot

Copy link
Copy Markdown
Collaborator Author

Shipped in v2.6.0.

Follow-up to #266.

  • The -T/--template flag and pr_template config option now actually reach GitHub; @maparent's surface was kept exactly as designed.
  • The Linear issue URL is appended after the template, so the pull request stays linked to its issue.

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