chore(github): add issue templates and enforce issue-link check on PRs#4189
chore(github): add issue templates and enforce issue-link check on PRs#4189Omee11 wants to merge 5 commits intopaperclipai:masterfrom
Conversation
…e PR check Close the issue intake gap so new issues arrive with structured context and new PRs consistently reference an issue. New files: - .github/ISSUE_TEMPLATE/bug_report.yml — YAML form with required env fields (version, Node, OS, adapter, DB mode, access context), frequency/impact dropdowns, PII warnings on every paste field, mandatory privacy checkbox - .github/ISSUE_TEMPLATE/feature_request.yml — pre-submission checklist, subsystem picker, impact-on-invariants section referencing AGENTS.md §5 - .github/ISSUE_TEMPLATE/enhancement.yml — improvements to existing behavior (current/proposed with examples, breaking-change assessment) - .github/ISSUE_TEMPLATE/docs_issue.yml — lightweight docs template - .github/ISSUE_TEMPLATE/config.yml — blank_issues_enabled: false, pinned contact links to Discord and GitHub Discussions - .github/workflows/auto-label-issues.yml — applies needs-triage to every new issue. Self-healing: creates the label on first run (it does not currently exist on the repo) - .github/workflows/require-issue-link.yml — soft check that PR bodies reference Closes/Fixes/Resolves #NNN; posts a comment and fails the check if missing. Contributors can override by explaining in the PR body Modified files: - AGENTS.md — add §11 "For AI Agents Filing Issues" (false-positive categories, pre-filing verification). Fixes an existing duplicate §11 numbering bug by renumbering the Fork-Specific section to §13 - CONTRIBUTING.md — add pointer to the issue templates at the top of the file, directing questions to Discord/Discussions Not touched: PULL_REQUEST_TEMPLATE.md (already strong), CODEOWNERS, or any existing workflow. Deferred to later PRs: stale bot, approval-label gates, PR template chooser, draft-PR auto-close. Model Used: Claude Opus 4.7 (1M context), claude-opus-4-7[1m] — used for research, drafting, and authoring of this commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR adds four YAML issue templates, a Confidence Score: 5/5Safe to merge — no runtime code changed, all workflows follow secure patterns, and prior review findings are addressed. All changes are in .github/ config and markdown files with no production code impact. The two workflows use minimal permissions, avoid untrusted input interpolation, and the pull_request_target usage is safe because no PR code is checked out. All previously flagged P1 issues are fixed. No files require special attention. Important Files Changed
Reviews (5): Last reviewed commit: "fix(require-issue-link): paginate commen..." | Re-trigger Greptile |
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, edited, reopened, synchronize] |
There was a problem hiding this comment.
Duplicate bot comments on each push
Because the trigger includes synchronize, every commit pushed to a PR that still lacks an issue link will post a brand-new "Missing issue link" comment — there is no deduplication guard. A contributor who pushes 10 commits before noticing the requirement ends up with 10 identical bot comments on their PR.
A common fix is to check whether the bot has already commented before posting, or to limit the trigger to only opened and edited (since contributors add the link by editing the PR body):
types: [opened, edited, reopened]If synchronize coverage is still wanted, the github-script step should search existing comments and skip posting when an identical message is already present.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/require-issue-link.yml
Line: 11
Comment:
**Duplicate bot comments on each push**
Because the trigger includes `synchronize`, every commit pushed to a PR that still lacks an issue link will post a brand-new "Missing issue link" comment — there is no deduplication guard. A contributor who pushes 10 commits before noticing the requirement ends up with 10 identical bot comments on their PR.
A common fix is to check whether the bot has already commented before posting, or to limit the trigger to only `opened` and `edited` (since contributors add the link by editing the PR body):
```yaml
types: [opened, edited, reopened]
```
If `synchronize` coverage is still wanted, the `github-script` step should search existing comments and skip posting when an identical message is already present.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed in commit c858806. Dropped synchronize from the trigger types — adding a Closes/Fixes/Resolves reference happens by editing the PR body (which fires edited), so the check still runs when the reference is added after opening, without the duplicate-comment loop. Went with the simpler trigger-scope fix rather than dedup-via-search to keep the workflow's logic minimal.
| permissions: | ||
| pull-requests: write | ||
| issues: write |
There was a problem hiding this comment.
Comment step silently fails on fork PRs
With the pull_request trigger, GitHub issues a read-only GITHUB_TOKEN to workflows running against PRs from forks, regardless of the permissions: block declared here. The issues.createComment call will return a 403 for any external contributor submitting from a fork, so the helpful "Missing issue link" message is never delivered — contributors see only a failed check with no explanation.
The standard approach for workflows that need write access on fork PRs is pull_request_target. Since neither step checks out the PR's code (the grep step only reads an env var and the github-script step only calls the REST API), switching the trigger is safe here:
on:
pull_request_target:
types: [opened, edited, reopened, synchronize]pull_request_target runs in the base-branch context and carries a full-permission token, while the PR body is still accessed via github.event.pull_request.body.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/require-issue-link.yml
Line: 13-15
Comment:
**Comment step silently fails on fork PRs**
With the `pull_request` trigger, GitHub issues a read-only `GITHUB_TOKEN` to workflows running against PRs from forks, regardless of the `permissions:` block declared here. The `issues.createComment` call will return a 403 for any external contributor submitting from a fork, so the helpful "Missing issue link" message is never delivered — contributors see only a failed check with no explanation.
The standard approach for workflows that need write access on fork PRs is `pull_request_target`. Since neither step checks out the PR's code (the grep step only reads an env var and the github-script step only calls the REST API), switching the trigger is safe here:
```yaml
on:
pull_request_target:
types: [opened, edited, reopened, synchronize]
```
`pull_request_target` runs in the base-branch context and carries a full-permission token, while the PR body is still accessed via `github.event.pull_request.body`.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed in commit c858806. Switched the trigger to pull_request_target. Confirmed this is safe here because neither step checks out PR-supplied code — the grep step only reads $PR_BODY from an env var, and the github-script step only calls the REST API with trusted context values (owner, repo, PR number). No untrusted content is executed or written at any point. The body of the PR also now calls this out explicitly under "Changes from Greptile review".
… trigger Addresses Greptile review feedback on PR paperclipai#4189 (two P1 issues): 1. Fork PRs receive read-only GITHUB_TOKEN under the pull_request trigger, so issues.createComment silently 403s for every external contributor. Switch to pull_request_target so the token is write-scoped. Safe here because neither step checks out PR-supplied code — the grep step only reads $PR_BODY from an env var and the github-script step only calls the REST API with trusted context values (owner, repo, PR number). 2. With synchronize in the trigger types, every new commit on a link-less PR posted a fresh duplicate "missing issue link" comment. Drop synchronize — contributors add the Closes/Fixes/Resolves reference by editing the PR body, which fires "edited", so the check still runs at the right moment without the dedup loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@greptileai rereview Both P1 issues from the initial review have been addressed in commit c858806:
Both threaded replies are on the original inline comments, and the PR body has a new "Changes from Greptile review" section documenting the fixes. |
Addresses Greptile P2 finding on PR paperclipai#4189: the `edited` trigger fires on both PR title and body edits, so a contributor editing only their title (fixing a typo, etc.) while the body still lacks a closing reference was getting a fresh "Missing issue link" bot comment each time. Comments accumulated across innocent title edits. Add a dedup guard that lists existing PR comments and skips posting when a Bot-authored comment starting with "## Missing issue link" is already present. The check itself still fails unconditionally when the link is missing — only the comment is skipped on repeats, so CI stays blocked until the contributor adds the reference. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@greptileai rereview The P2 dedup-guard concern is addressed in commit 1889a67. Added a guard that lists existing PR comments and skips posting when a Bot-authored comment starting with The check itself still fails unconditionally when the issue link is missing ( |
| if printf '%s' "$PR_BODY" | grep -qiE '(closes|fixes|resolves)\s+#[0-9]+'; then | ||
| echo "found=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "found=false" >> "$GITHUB_OUTPUT" | ||
| fi |
There was a problem hiding this comment.
Regex misses several valid GitHub closing keywords
GitHub's full set of auto-closing keywords includes all tenses: close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved. The current pattern only covers the present-tense third-person forms (closes, fixes, resolves), so a contributor who writes Fixed #123 or Closed #4188 will receive a failing check and a bot comment even though GitHub would correctly close the issue on merge.
| if printf '%s' "$PR_BODY" | grep -qiE '(closes|fixes|resolves)\s+#[0-9]+'; then | |
| echo "found=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "found=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| if printf '%s' "$PR_BODY" | grep -qiE '(close[sd]?|fix(e[sd])?|resolve[sd]?)\s+#[0-9]+'; then |
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/require-issue-link.yml
Line: 36-40
Comment:
**Regex misses several valid GitHub closing keywords**
GitHub's full set of auto-closing keywords includes all tenses: `close`, `closes`, `closed`, `fix`, `fixes`, `fixed`, `resolve`, `resolves`, `resolved`. The current pattern only covers the present-tense third-person forms (`closes`, `fixes`, `resolves`), so a contributor who writes `Fixed #123` or `Closed #4188` will receive a failing check and a bot comment even though GitHub would correctly close the issue on merge.
```suggestion
if printf '%s' "$PR_BODY" | grep -qiE '(close[sd]?|fix(e[sd])?|resolve[sd]?)\s+#[0-9]+'; then
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed in commit 37f4f54. Applied the suggested regex verbatim. Sanity-checked locally against all 9 keyword variants (close/closes/closed, fix/fixes/fixed, resolve/resolves/resolved), case-insensitive forms (Close, FIXED), and non-matches (closing, fixes without #, bug #1) — all behave as expected. Thanks for catching this.
Addresses Greptile follow-up finding on PR paperclipai#4189. The previous regex only matched the third-person singular present tense (closes/fixes/resolves), so a contributor writing "Fixed paperclipai#123" or "Closed paperclipai#4188" would receive a failing check and a bot comment even though GitHub itself would correctly close the issue on merge. Expanded to match GitHub's full set of auto-closing keywords across all supported tenses: close/closes/closed, fix/fixes/fixed, and resolve/resolves/resolved. Case-insensitive flag already in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@greptileai rereview Regex scope fix applied in commit 37f4f54 — now matches GitHub's full set of auto-closing keywords across all tenses (close/closes/closed, fix/fixes/fixed, resolve/resolves/resolved). Reply posted on the inline comment with sanity-check details. |
Addresses Greptile P2 follow-up on PR paperclipai#4189. The previous dedup guard called listComments with per_page: 100 and no pagination loop, so on a PR with more than 100 comments the bot's original "Missing issue link" message could fall outside the fetched page — alreadyCommented would resolve to false and duplicates would reappear on the next edited or reopened event. Swap the direct call for github.paginate(listComments, ...), which walks every page of comments automatically. The returned value is the same flat array of comments, so the downstream .some(...) predicate is unchanged. API cost stays bounded: workflow runs on three events only (opened, edited, reopened) so even a PR with 1,000+ comments costs at most ~10 REST calls per trigger. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@greptileai rereview Pagination P2 addressed in commit 9e4fa34. Swapped the direct |
Thinking Path
What Changed
New files under
.github/ISSUE_TEMPLATE/:bug_report.yml— required fields for Paperclip version, Node, OS, install method, adapter(s), DB mode (PGlite / external Postgres), access context (board / agent). Frequency + impact dropdowns. PII warnings on every paste field plus a mandatory privacy checkboxfeature_request.yml— pre-submission checklist, subsystem picker (server / ui / packages/db / packages/shared / packages/adapters / packages/plugins / cross-cutting), impact-on-invariants field referencingAGENTS.md§5enhancement.yml— for improvements to existing behaviour (before/after, breaking-change check)docs_issue.yml— lightweight, 4 fieldsconfig.yml—blank_issues_enabled: falseand pinned contact links to Discord and GitHub DiscussionsNew files under
.github/workflows/:auto-label-issues.yml— appliesneeds-triageto every new issue. Self-healing: creates the label on first run if it doesn't exist (it currently doesn't). SHA-pinnedactions/github-script@3a2844b7...(v9.0.0). No shell interpolation of untrusted input — all logic in the github-script JS sandboxrequire-issue-link.yml— scans PR body forCloses/Fixes/Resolves #NNN; if missing, posts a comment and fails the check. Soft check — contributors can add a rationale line in the PR body for genuinely issue-less PRs. Untrusted input (github.event.pull_request.body) is bound to$PR_BODYviaenv:and scanned withgrep, never interpolated directly into the shell command (follows the documented safe pattern). Usespull_request_targetand only[opened, edited, reopened]trigger types — see "Changes from Greptile review" belowModified files:
AGENTS.md— adds new §11 "For AI Agents Filing Issues" listing common false positives (API quota / rate limit, auth failures in user's own config, external plugin errors) that agents should not file against Paperclip. Also fixes an existing duplicate-§11 numbering bug (both "Definition of Done" and "Fork-Specific: HenkDz/paperclip" were §11) by renumbering them to §12 and §13 respectivelyCONTRIBUTING.md— one-line pointer at the top of the file directing question-askers to Discord/Discussions and bug-filers to the template chooserDeliberately NOT touched:
.github/PULL_REQUEST_TEMPLATE.md— already rich (Thinking Path, Model Used, full checklist), would be a regression to weaken itdocker.yml,e2e.yml,pr.yml,refresh-lockfile.yml,release.yml,release-smoke.yml) — no interaction, the two new workflows run alongsideCODEOWNERSHow the issue templates work (and the concrete benefits for Paperclip)
Mechanically, YAML issue forms ≠ markdown templates. GitHub renders
.ymltemplates as typed UI forms, not a textarea pre-filled with boilerplate. The submit button stays disabled until every field markedrequired: truehas content. This is the key mechanism: it converts "please include these details" (a polite request that lazy filers and AI agents ignore) into "you cannot submit without these details" (a hard gate enforced by the GitHub UI, before anything reaches your tracker).blank_issues_enabled: falsecloses the bypass route. GitHub normally shows an "Open a blank issue" button underneath the template chooser — that button is where most of the unstructured agent noise currently slips through. With the config, every issue must pick one of the four templates; there is no blank path.Concrete downstream effects for the paperclip tracker:
AGENTS.md§11 addition listing common false positives (API quota, auth, external plugin errors), those filings either arrive with enough context to see they're not Paperclip's problem (→ quick close with a link toAGENTS.md§11) or they don't arrive at all.bug+needs-triage, every feature withenhancement+needs-triage, every docs issue withdocumentation+needs-triage. Combined with the subsystem picker in feature/enhancement templates,issues?q=label:needs-triage+label:bugor an equivalent subsystem filter gives you a tight queue instead of the full backlog.AGENTS.md§5 directly (company scoping, atomic issue checkout, approval gates, budget hard-stop, activity logging) and force the filer to answer "does this affect any of them?" That single field tends to surface scope-creep and invariant-conflict issues before a maintainer has to read the body and work it out.needs-triageas a queue marker, not decoration. Once merged,issues?q=label:needs-triagebecomes a clean "inbox" view — separate from the ~1,100 historic backlog. Every issue reviewed and classified (or closed) leaves that queue, so triage work has a concrete end state rather than being open-ended. The filter is the forcing function that makes triage feel finite.Changes from Greptile review (commit c858806)
Greptile's initial review flagged two P1 issues in
require-issue-link.yml, both fixed:synchronize— every pushed commit on a link-less PR was posting a fresh identical "Missing issue link" comment. Droppedsynchronizefrom the trigger list. Adding aCloses/Fixes/Resolvesreference happens by editing the PR body, which firesedited, so the check still runs when the reference is added after opening — no dedup loop needed.GITHUB_TOKENunderpull_request— the comment step would silently 403 for every external contributor, meaning they never saw the helpful message. Switched topull_request_target. Safe here because neither step checks out PR-supplied code: the grep step reads$PR_BODYfrom an env var, and the github-script step only calls the REST API with trusted context values (owner, repo, PR number).Verification
Local checks (all green on this branch against current
upstream/master):python3 -c "import yaml; yaml.safe_load(...)"on all 7 new YAML files — all parsepnpm -r typecheck— exit 0pnpm build— exit 0pnpm test:run— has 40 failing tests. I verified these are pre-existing by stashing this branch's changes and running the same failing test files against cleanupstream/master— they fail identically there (TypeError: localStorage.clear is not a functioninui/src/pages/Routines.test.tsxandui/src/pages/InviteLanding.test.tsx, which are test-environment setup issues unrelated to this PR's.github/+ markdown-only diff). Flagging here so CI noise doesn't obscure the actual signalPost-merge reviewer checks:
github.com/paperclipai/paperclip/issues/new/choose. Expect four template cards + two contact links. Expect no "Open a blank issue" option. Click into each — required fields should block submission when emptyauto-label-issues.yml: open a fresh test issue after merge. Expect aneeds-triagelabel to appear (the workflow creates the label on its first run if it doesn't exist). Subsequent issues reuse the same labelrequire-issue-link.yml: this PR itself demonstrates the happy path — the body containsCloses #4188, so the check will pass. To verify the negative path, temporarily remove that line, observe the workflow post its comment and fail, then restore it. After merge, future PRs without a closing reference will surface the comment automaticallypr.ymlCI (policy / verify / e2e jobs) stays green — none of those jobs touch.github/contentRisks
auto-label-issuesworkflow creates theneeds-triagelabel on first run. Colour chosen:fbca04(yellow). If that colour clashes with something existing, the label still creates; only the visual clashes. Easy to adjust post-mergerequire-issue-link.ymlon in-flight PRs: will run on currently-open PRs when they're next edited/reopened and comment on any withoutCloses/Fixes/Resolves #NNN. Becausesynchronizeis intentionally excluded, a push alone does not retrigger the check — only an opened/edited/reopened event will. The check is soft (contributors can override by adding a one-line rationale in the PR body)gsd-build/get-shit-done's templates but dialled softer (no "unchecked boxes are an immediate close" language, no auto-close behaviour). Happy to dial further up or down — this PR is the starting point, not the destinationapproved-featureetc.), multi-typed PR template chooser, draft-PR auto-close. Mentioning only so you know what's not being proposed hereModel Used
Claude Opus 4.7 (1M context) — provider Anthropic, exact model ID
claude-opus-4-7[1m], 1,000,000-token context window, extended thinking mode with tool use. Used for: research (readinggsd-build/get-shit-done/.githubas reference, studying this repo'sAGENTS.md§5 invariants and existing PR template), drafting all 9 files, writing the tracking issue body, authoring this PR description, and applying the Greptile-flagged fixes in commitc8588061. All code reviewed and approved by a human (@Omee11) before pushing.Checklist
Routines.test.tsx/InviteLanding.test.tsxare unrelated — verified against cleanupstream/master, see Verification).github/+ markdown only; no runtime code changed)AGENTS.md§11 +CONTRIBUTING.mdpointer)Closes #4188