Ship PR
Risk → merge authority
Self-assess; user policy overrides.
Kent's standing policy (2026-08-08): auto-ship (squash-merge + verify deploy) once AI reviewer feedback is addressed and CI is green, unless high risk. High risk still parks ready-for-review unless merge authority was granted explicitly.
- Low — green CI; nits ignorable; squash-merge when policy allows.
- Medium — wait for AI reviewer(s); address valid feedback (ignore insignificant nits / already-fixed / wrong); then merge when policy allows.
- High — leave ready-for-review unless the user granted merge authority.
AI reviewers
Prefer Cursor Bugbot (Cursor Bugbot check / cursor[bot] review
comments). Never comment bugbot run or cursor review yourself, including via
gh or GitHub request as kody-bot. Trigger with kody:@kentcdodds/bugbot using
{ prUrl } or { owner, repo, prNumber }. Do not pass a GitHub account.
import triggerBugbot from 'kody:@kentcdodds/bugbot'
await triggerBugbot({ prUrl })
CodeRabbit: if it is rate-limited, errored, or otherwise unavailable, do not wait on it for low/medium risk — proceed with Bugbot + CI. Only wait on CodeRabbit when the change is high risk (or the user explicitly asks).
Loop
- Mark ready —
kody:@kentcdodds/github/pr/set-review-status{ prUrl, status: 'ready' }(or owner/repo/prNumber). - Wait for CI —
gh pr checks(or composeloop-on-ci/fix-ci). - Fix failures; for medium+, wait on AI reviewer(s) (Bugbot first; see
above for CodeRabbit) and address valid feedback. Rebase only when actually
unmergeable. For medium+, also run
npm run control-kody -- preview(ornpm run preview:manual-test) as the seeded user with data for this change (--request/ session cookie; see control-kody and preview-manual-test). After merge,npm run control-kody -- health --origin https://kody.codes --sha <merge>. - Green + (medium+: valid feedback cleared) → break.
- Push → repeat.
Gates ≠ CI
Blocked on soak / parity CHECK / calendar gate → end the run and schedule a
wake (execute + workflows.create({ runAt, idempotencyKey }) calling
createRun). Don't sleep-poll or code-thrash an intentional time window.
Leftovers that wait on that gate need a GitHub issue (Cleanup: title); see
cleanup-after-migrations.
Batch related expand steps into fewer PRs when risk posture allows.
Merge / deploy
When policy + risk allow: squash-merge via kody:@kentcdodds/github/pr/merge
{ prUrl, mergeMethod: 'squash' }, watch deploy. Useful: pr/get-checks,
request, graphql on the same package.
Done → Discord
Always summarize (merged or not) via kody:@kentcdodds/discord/send-shipped-pr.
That export formats kind ship-pr, fetches Cursor token cost from the usage API,
and includes the model you pass. Never invent a dollar figure or a model id.
When the work deployed user-visible pages, put clickable links to those pages in
extras (see below) so Kent can open the live result from Discord.
title (required): a human headline of the change itself so the Discord post
is glanceable. Example: OpenAPI spec fetches now count against daily quota. Do
not use ship owner/repo#N as the title — repo, PR, and agent already
appear as links.
difficulty (required): 'Easy' | 'Medium' | 'Hard'. Always pass it.
Distinct from Risk (merge authority). Easy = small/localized; Medium = several
files or real behavior change; Hard = architecture, migrations, subtle
correctness, or wide blast radius. The Discord export renders this on its own
line.
agentId (required):
- In a Cursor Cloud Agent VM, read it from the metadata socket:
curl -fsS --unix-socket "${CURSOR_AGENT_SOCKET:-/run/cursor/api.sock}" http://cursor-agent/v1/meta-data/agent/id - Otherwise pass the
bc-id from the agent URL you were launched as (https://cursor.com/agents/{id}).
model (deterministic — never infer):
- In a Cursor Cloud Agent VM, read the model that served this turn from the
metadata socket (not a guess from writing style):
curl -fsS --unix-socket "${CURSOR_AGENT_SOCKET:-/run/cursor/api.sock}" http://cursor-agent/v1/meta-data/turn/modelIf you selected Auto, this is the concrete model that served, notAuto. See https://cursor.com/docs/cloud-agent/metadata - Outside a managed VM, pass the
model.idused at create/launch if you still have that record. - If the key is missing (
404/ empty), omitmodelso the export postsModel pending— do not invent one.
Deployed pages (when the work actually shipped UI/routes):
- If merge+deploy produced live pages people can click, put those URLs in
extrasas Discord markdown links (one per line item). Prefer the specific routes/pages that changed, not only the site root. - Skip this when nothing user-visible deployed (library-only, docs-only with no hosted page, parked/blocked, or deploy not reached).
- Do not invent URLs. Only include pages you verified or that the deploy output / PR preview / production URL clearly maps to.
import sendShippedPr from 'kody:@kentcdodds/discord/send-shipped-pr'
export default async function main() {
return sendShippedPr({
agentId: 'bc-…', // metadata socket or launch URL
model: 'grok-4.6', // metadata turn/model (deterministic)
title: 'OpenAPI spec fetches now count against daily quota',
difficulty: 'Medium',
status: 'Shipped', // or Parked / Blocked
summary: 'One-screen what shipped and why it is done.',
prUrl: 'https://github.com/owner/repo/pull/123',
repo: 'owner/repo',
extras: [
'CI green',
'[Account](https://kody.codes/account)', // only when that page actually deployed
'[Connect](https://kody.codes/connect)',
],
})
}