diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b2573c4d..a6d8f0866 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -917,7 +917,9 @@ fix, each with its reason in the closing comment. An issue we intend to fix later stays open under the `parked` label with one comment saying where it is tracked (a TODOS.md theme or a tracking issue) and what fixes it. An agent triaging for the maintainer posts these comments under its bot identity -(`capy-ai[bot]` on Capy), never the maintainer's own account. The full rule, +(`capy-ai[bot]` on Capy), never the maintainer's own account. On Capy, comment only through +`tools.pr.comment`, never `gh pr comment`, `gh issue comment` or `gh api`, because +`gh` runs as the maintainer. The full rule, including the labels and the close reasons, is in [docs/triage.md](docs/triage.md). ## Community PR triage (wave process) diff --git a/changelog.d/capy-gst-1417-followup-gh-tool.md b/changelog.d/capy-gst-1417-followup-gh-tool.md new file mode 100644 index 000000000..5b7725959 --- /dev/null +++ b/changelog.d/capy-gst-1417-followup-gh-tool.md @@ -0,0 +1 @@ +- **On Capy, agents comment and request reviews only through Capy's PR tools, never `gh`.** docs/triage.md, CONTRIBUTING.md, docs/captain.md, docs/recommended-harness.md and /modernize now say that comments and tags go only through `tools.pr.comment` and review requests only through `tools.pr.edit`, both acting as `capy-ai[bot]`. `gh pr comment`, `gh issue comment`, `gh api` and `gstack-post` are never used for them, because `gh` on Capy runs as the maintainer's own account. An unattended /modernize run pushes, closes and posts nothing. (GST-1417) diff --git a/docs/captain.md b/docs/captain.md index 77ffde3d7..02f9d33d2 100644 --- a/docs/captain.md +++ b/docs/captain.md @@ -248,8 +248,9 @@ dependencies, CI permissions and weakened tests before anything runs it, and do a fork's "tests pass": run a focused review and the free tier at the port head before the port opens. When the contributor's fix is right, land it with their authorship kept (cherry-pick, `Co-authored-by`, the lane's own fixes as separate commits on top of -theirs) and close the original with a specific thank-you that links the port, posted under -the lane's bot identity (`capy-ai[bot]`), never the owner's own account. Closing a +theirs) and close the original with a specific thank-you that links the port, posted as +`capy-ai[bot]` through `tools.pr.comment`, never `gh pr comment`, `gh issue comment` or `gh api` (`gh` runs +as the owner). Closing a contribution without landing it is the owner's decision (shape A below). The repository's own statement of the porting steps is [CONTRIBUTING.md](../CONTRIBUTING.md#porting-a-contributors-pr). diff --git a/docs/recommended-harness.md b/docs/recommended-harness.md index 9aeb76ca5..b5348f167 100644 --- a/docs/recommended-harness.md +++ b/docs/recommended-harness.md @@ -351,7 +351,7 @@ on a few points. Ship the one that fits the repository; both are supported. | Small fixes | one PR each, batched by the train | batched into wave PRs, since serial slots cost about 30 minutes each | | After the merge | the main backstop gates any push a train did not cover, and freezes the queue on red | the controller watches main's test, e2e and release runs until the release publishes; a red main freezes the queue until root-caused | | Severe triage | the Captain watches issues and PRs and starts a thread per unowned severe item | a scheduled severe sweep (every 4 to 8 hours) with a fixed severity bar, dedupe against owned lanes, one hotfix thread per batch | -| Contributor PRs | ported onto a crew branch with the author's commits kept and `Co-authored-by`; the fork PR is closed with a thank-you that links the port, posted under the bot identity (`capy-ai[bot]`) | landed through a wave PR with "Contributed by @handle", `Co-Authored-By` and `Supersedes` lines | +| Contributor PRs | ported onto a crew branch with the author's commits kept and `Co-authored-by`; the fork PR is closed with a thank-you that links the port, posted as `capy-ai[bot]` through `tools.pr.comment`, never `gh` | landed through a wave PR with "Contributed by @handle", `Co-Authored-By` and `Supersedes` lines | | Privacy | the pregate's sealed-output and secret scans before every push | plus a per-user denylist grep (names, hostnames, identifying counts from private sources) before any public artifact | ## Known Capy limits diff --git a/docs/skills.md b/docs/skills.md index 58b3f91a8..a96f83d09 100644 --- a/docs/skills.md +++ b/docs/skills.md @@ -832,7 +832,7 @@ Claude: Claim: #2187 caches the skill census so `bun run skill:check` stops Verdict: CLOSE. The base solves the problem another way (a2f9c01). Nothing was pushed and the PR stays open; say "close it" and I will post the reason - and the superseding commit, with thanks to its author. + and the superseding commit from the bot account, with thanks to its author. ``` Three verdicts, one chosen before anything is pushed. **MODERNIZE**: the claim still diff --git a/docs/triage.md b/docs/triage.md index 94e535df3..2ca07e8a1 100644 --- a/docs/triage.md +++ b/docs/triage.md @@ -7,12 +7,17 @@ feature requests into TODOS.md themes with a "not rejected" note, and reporters still saw a closed issue with no fix. Those were reopened on 2026-10-25 under the `parked` label, and this page is the rule going forward. -An agent triaging for the maintainer may post every comment this page -describes, tag people, request reviews and leave short notes for outside -authors, but only under its bot identity (`capy-ai[bot]` on Capy), never the -maintainer's own GitHub account. An agent with no bot identity writes the -comment as a draft for the maintainer instead. GitHub comments are the only way -an agent reaches people for the maintainer: it never sends them email, chat +An agent triaging for the maintainer may post every comment this page describes, +tag people, request reviews and leave short notes for outside authors, but only +under its bot identity (`capy-ai[bot]` on Capy), never the maintainer's own +GitHub account. On Capy, comment and tag people only through Capy's PR comment +tool (`tools.pr.comment`), and request reviews only through `tools.pr.edit`; +both act as `capy-ai[bot]`. Never comment, tag or request a review with +`gh pr comment`, `gh issue comment`, `gh api` or `gstack-post` on a Capy +machine: `gh` there runs as the maintainer's own account. Elsewhere, comment through `gh` only +when `gh api user --jq .type` prints `Bot`. A comment no bot-identity tool can +post is a draft for the maintainer instead. GitHub comments are the only way an +agent reaches people for the maintainer: it never sends them email, chat messages, DMs or social posts, and never reads the maintainer's inbox (Garry, 2026-11-04: "never send emails or messages to human beings on my behalf ever"; amended 2026-11-05: "Oh you can leave comments on GitHub"). diff --git a/modernize/SKILL.md b/modernize/SKILL.md index 8b48eb475..7322016ed 100644 --- a/modernize/SKILL.md +++ b/modernize/SKILL.md @@ -116,7 +116,9 @@ your own; this skill is for a branch that has gone stale, often someone else's. - A comment you post on the PR (a closing note, a thank-you to its author) goes under your bot identity (`capy-ai[bot]` on Capy), never the maintainer's own account; with no bot identity, it is a draft in your report for the person to - post. Never email, chat or DM its author. + post. On Capy, post through `tools.pr.comment` (it posts as `capy-ai[bot]`), + never `gh pr comment`, `gh issue comment` or `gh api`, which run as the + maintainer. Never email, chat or DM its author. - Textual cleanliness is not currency. "Git reports no conflicts" says nothing about whether the change still makes sense; a PR that applies cleanly to code whose contract moved is more dangerous than one that conflicts. @@ -181,7 +183,7 @@ Pick exactly one and state it with the evidence from Steps 1 and 2: Show the table row you chose and the three sentences from Step 1, then STOP and ask (AskUserQuestion) before any push, close or comment. If the session is -unattended, the verdict and its evidence are the handoff and nothing is pushed. +unattended, the verdict and its evidence are the handoff and nothing is pushed, closed or posted. ## Step 4 (MODERNIZE only): Rebase and prove intent survived diff --git a/modernize/SKILL.md.tmpl b/modernize/SKILL.md.tmpl index 288aba39d..a8e4992a9 100644 --- a/modernize/SKILL.md.tmpl +++ b/modernize/SKILL.md.tmpl @@ -45,7 +45,9 @@ your own; this skill is for a branch that has gone stale, often someone else's. - A comment you post on the PR (a closing note, a thank-you to its author) goes under your bot identity (`capy-ai[bot]` on Capy), never the maintainer's own account; with no bot identity, it is a draft in your report for the person to - post. Never email, chat or DM its author. + post. On Capy, post through `tools.pr.comment` (it posts as `capy-ai[bot]`), + never `gh pr comment`, `gh issue comment` or `gh api`, which run as the + maintainer. Never email, chat or DM its author. - Textual cleanliness is not currency. "Git reports no conflicts" says nothing about whether the change still makes sense; a PR that applies cleanly to code whose contract moved is more dangerous than one that conflicts. @@ -110,7 +112,7 @@ Pick exactly one and state it with the evidence from Steps 1 and 2: Show the table row you chose and the three sentences from Step 1, then STOP and ask (AskUserQuestion) before any push, close or comment. If the session is -unattended, the verdict and its evidence are the handoff and nothing is pushed. +unattended, the verdict and its evidence are the handoff and nothing is pushed, closed or posted. ## Step 4 (MODERNIZE only): Rebase and prove intent survived diff --git a/test/modernize-skill.test.ts b/test/modernize-skill.test.ts index ff4ade007..e5ff6386a 100644 --- a/test/modernize-skill.test.ts +++ b/test/modernize-skill.test.ts @@ -33,14 +33,14 @@ describe('/modernize skill', () => { expect(skill.indexOf('## Step 3: The disposition')).toBeLessThan(skill.indexOf('## Step 4 (MODERNIZE only)')); expect(skill.indexOf('## Step 4 (MODERNIZE only)')).toBeLessThan(skill.indexOf('## Step 5 (MODERNIZE only)')); expect(flat).toContain('STOP and ask (AskUserQuestion) before any push, close or comment.'); - expect(flat).toContain('If the session is unattended, the verdict and its evidence are the handoff and nothing is pushed.'); + expect(flat).toContain('If the session is unattended, the verdict and its evidence are the handoff and nothing is pushed, closed or posted.'); }); test('never closes a PR on its own', () => { expect(flat).toContain('You never close a PR.'); expect(flat).toContain('Without explicit permission in this session, the PR stays open.'); expect(flat).toContain('The PR stays open until someone with the authority says so.'); - expect(flat).toContain('A comment you post on the PR (a closing note, a thank-you to its author) goes under your bot identity (`capy-ai[bot]` on Capy), never the maintainer\'s own account; with no bot identity, it is a draft in your report for the person to post.'); + expect(flat).toContain('A comment you post on the PR (a closing note, a thank-you to its author) goes under your bot identity (`capy-ai[bot]` on Capy), never the maintainer\'s own account; with no bot identity, it is a draft in your report for the person to post. On Capy, post through `tools.pr.comment` (it posts as `capy-ai[bot]`), never `gh pr comment`, `gh issue comment` or `gh api`, which run as the maintainer.'); expect(skill).not.toMatch(/gh pr close/); });