name: pinpoint-pr-workflow description: Full PR lifecycle for PinPoint — commit, push, CI monitoring, review-comment handling (via MCP), readiness labeling, gate-enforced merge. Use when committing changes, opening PRs, watching CI, addressing review comments, or merging.
PinPoint PR Workflow
End-to-end pipeline from "I have changes" to "merged in main". Replaces the deprecated pinpoint-commit, pinpoint-ready-to-review, and pinpoint-github-monitor skills.
When to use — pick your entry phase
- Uncommitted changes in tree → Phase 1: Commit
- Local commits, no PR yet → Phase 2: PR
- PR open, CI not yet green-and-clean → Phase 3: Review
ready-for-reviewlabel applied → Phase 4: Merge
Phase 1: Commit
1.1 Branch validation
- Verify NOT on main:
git rev-parse --abbrev-ref HEAD≠main. - Verify branch follows naming convention:
feature/*,fix/*,chore/*,docs/*,test/*,refactor/*. - Verify based on current main:
git merge-base HEAD origin/mainis recent. - Verify NO
git rebase origin/mainever — commandment 18 (usegit merge origin/maininstead).
1.2 Pre-commit validation
Default to pnpm run check (~12s; covers type, lint, format, unit tests, yamllint, actionlint, ruff, shellcheck). Use pnpm run preflight (full + integration) before commit for non-trivial changes, especially: migrations, security/auth changes, server actions, middleware.
1.3 E2E selection
Use this matrix based on git diff --name-only --staged. Never run e2e:full / e2e:all locally — the full suite is CI's job. Locally, run only targeted specs (pnpm exec playwright test <spec> --project=chromium) while writing them or iterating on a feature they touch.
| Changed file patterns | Recommended local check |
|---|---|
src/app/**/page.tsx, src/app/**/layout.tsx, src/app/(auth)/** |
Targeted spec(s) for the affected flow while iterating; CI runs the full suite |
src/components/issues/*, src/components/machines/*, src/server/actions/*, src/lib/supabase/* |
Targeted spec(s) while iterating; CI runs the full suite |
supabase/migrations/*, src/server/db/schema.ts |
pnpm run preflight (includes smoke E2E) |
src/components/ui/*, src/lib/* (non-supabase) |
pnpm run smoke (~60s; already in preflight) |
docs/**, *.test.ts, *.spec.ts, .agents/**, scripts/* |
skip additional E2E |
1.4 Commit message
Conventional commits: <type>(<scope>): <description>.
Types: feat, fix, refactor, chore, docs, test, style.
PinPoint scopes: issues, machines, auth, ui, db, e2e, agents, workflow, hooks, forms, notifications, etc. — use the most-affected area.
1.5 Push
If branch has no upstream: git push -u origin <branch-name>. Else: git push.
Verify upstream tracks the branch itself, NOT main: git branch -vv should show [origin/<your-branch>].
Phase 2: PR
2.1 Open the PR
Prefer MCP create_pull_request for typed argument handling. Or use gh pr create if you're already in a shell context.
MCP example:
- Tool:
mcp__github__create_pull_request - Args:
owner: "timothyfroehlich",repo: "PinPoint",title: "<title>",body: "<description>",head: "<branch>",base: "main",draft: false(ready-for-review by default — see 2.3)
2.2 PR description template
## Summary
- [1-3 bullets summarizing what changed and why]
## Test Plan
- [ ] [bulleted markdown checklist of TODOs for testing the PR]
## Related Issues
Closes #N (if applicable)
2.3 Draft vs ready
Default: open ready-for-review, not draft — CI runs the same on drafts, so draft gates nothing. Use draft ONLY while you're still iterating, when you want title/description feedback first, or when you've said you're pausing mid-task. Don't reflexively open as draft.
Phase 3: Review (CI + label)
3.1 Watch CI
Use Monitor tool with pr-watch.py:
Monitor(
command: "./scripts/workflow/pr-watch.py <PR>",
description: "CI watch for PR #<PR>",
persistent: false,
timeout_ms: 3600000
)
Exit 0 = all passed. Exit 1 = failure — read tmp/gh-monitor/failure-<RUN_ID>.md.
3.2 Check for review comments
If the PR has review comments (from Tim or another agent), read them via MCP:
mcp__github__pull_request_read(
method: "get_review_comments",
owner: "timothyfroehlich",
repo: "PinPoint",
pullNumber: <PR>,
perPage: 100
)
Returns array of review threads. Each thread has:
is_resolved(snake_case! not camelCase)is_outdatedcomments[]withpath,line,body,author.login,html_url, and crucially a thread node ID for resolving
3.3 Address review comments
For each unresolved thread, evaluate critically. Not all suggestions warrant code changes.
To fix: edit code, commit, push, then resolve the thread.
To decline: post a one-sentence justification reply AND resolve the thread:
- Reply:
mcp__github__add_reply_to_pull_request_comment(
owner: "timothyfroehlich",
repo: "PinPoint",
pullNumber: <PR>,
commentId: <commentId from thread>,
body: "Ignored: <one-sentence justification>. —Claude-<YourName>"
)
- Resolve:
mcp__github__pull_request_review_write(
method: "resolve_thread",
threadId: <PRRT_kwDOxxx from thread>,
owner: "timothyfroehlich",
repo: "PinPoint",
pullNumber: <PR>
)
(Owner/repo/pullNumber not actually used for resolve_thread but tool requires them per schema.)
Sign replies with your agent name (—Claude-Plunger, —Claude-Spinner, etc.).
3.4 Apply ready-for-review label
Once CI green + zero unresolved review threads + no merge conflict:
- Read current labels via
pull_request_read(method: "get")and extract.labels[]. - Build new labels array: existing labels +
"ready-for-review". - Apply:
mcp__github__issue_write(
method: "update",
owner: "timothyfroehlich",
repo: "PinPoint",
issue_number: <PR>,
labels: [<existing>, "ready-for-review"]
)
NOTE: PR labels are added via the issues endpoint. labels parameter is full-replacement, so read current labels first to avoid clobbering.
The label is a hint to Tim that the PR is ready. merge-pr.sh re-checks all gates at merge time regardless.
Phase 4: Merge
Direct gh pr merge and MCP merge_pull_request are blocked by hook. Use merge-pr.sh.
4.1 Invoke
bash scripts/workflow/merge-pr.sh <PR>
Flags (stackable, order-independent):
--dry-run— preview only, no action.--bypass-merge-requirements— bypasscigate AND pass--admintogh pr merge, overriding GitHub branch-protection rules. Requires manual permission approval.
The no_conflict gate is NEVER bypassable — GitHub rejects conflicting merges regardless of --admin.
4.2 Interpret output
Script emits structured status tokens:
| Token | Meaning | What to do |
|---|---|---|
PASS: <gate>: <state> |
Gate passed | Continue |
FAIL: <gate>: <state> |
Gate failed | Fix underlying issue, push, retry |
WAIT: <gate>: <state> |
Transient (e.g., GitHub computing mergeable) | Retry merge-pr.sh after a few seconds |
BLOCK: <gate>: <state> |
State mismatch requiring action (e.g., merge conflict) | Resolve, push, retry |
On any FAIL: script removes ready-for-review label if present (the label's contract is "click-merge-without-thinking"; if a gate fails at merge time, that contract is broken). Exit 1.
On all PASS: script captures head SHA, calls gh pr merge <PR> --squash --match-head-commit=<sha>. TOCTOU-safe — if a new commit lands between gate check and merge, GitHub rejects the merge (--match-head-commit mismatch). Branch deletion is handled by the repo's auto-delete-branches setting, not by the merge command — passing --delete-branch from a worktree fails local cleanup because main is held by the root checkout.
4.3 Escape hatches
--bypass-merge-requirements — for CI/branch-protection issues:
- A required check is failing for known-irrelevant reasons (infrastructure flake, unrelated job) AND you've manually verified the change is safe
- An emergency hotfix where waiting for CI is not acceptable
Do NOT bypass when:
- Merge conflict exists (
no_conflictgate is never bypassable; conflicts can't be ignored) - You haven't manually verified the underlying state. The flag requires manual permission approval in the chat — treat the approval prompt as a "are you sure?" checkpoint.
4.4 Bypass the hook (emergency only)
If you absolutely need to bypass the hook (e.g., merge-pr.sh itself is broken and you have a hotfix to ship):
touch .claude-merge-bypass
gh pr merge <PR> --squash
The sentinel is single-use — deleted on the next merge attempt. Document in commit message WHY you bypassed.
4.5 Dependabot PRs: rebase before merging back-to-back
When two or more Dependabot PRs that both touch pnpm-lock.yaml (or any lockfile) are open simultaneously, merging them in succession without rebasing the second-and-later PRs can silently break the lockfile.
The trap: each Dependabot PR's lockfile diff adds entries in slightly different alphabetical zones based on its own snapshot of main. After the first PR merges, git's textual three-way merge of the second PR doesn't see a conflict because the additions live in non-overlapping line ranges — but both PRs may add the same transitive dep (e.g., brace-expansion@5.0.6). The squash-merge produces a lockfile with a duplicated mapping key, which pnpm install --frozen-lockfile rejects with ERR_PNPM_BROKEN_LOCKFILE. Every new PR's Setup Dependencies then fails until main is fixed.
Why rebase-strategy: auto in .github/dependabot.yml doesn't save you: "auto" means Dependabot rebases when the dependency version is out of date, not when the lockfile region has shifted under it. Two independent Dependabot PRs against the same main can both stay "current" by Dependabot's definition while their lockfile diffs collide on merge.
Rule: when merging the first of two or more Dependabot PRs that both touch a lockfile, comment @dependabot rebase on each remaining Dependabot PR before merging it. Dependabot regenerates the lockfile against post-first-merge main and the duplicate is deduped automatically. Wait for the rebased CI to pass before invoking merge-pr.sh on the second PR.
Casework: 2026-05-19 — PRs #1379 and #1381 each added brace-expansion@5.0.6: to packages: independently. Both merged within ~1 minute. Main's Setup Dependencies broke until a manual dedup of pnpm-lock.yaml was bundled into PR #1383 alongside that PR's primary E2E locator fix.
Quick triage check before merging the second of two open Dependabot PRs:
# How many commits is the PR's branch behind origin/main?
# behind_by > 0 means the PR's lockfile snapshot predates current main.
pr_branch=$(gh pr view <second_pr> --json headRefName --jq .headRefName)
gh api "repos/{owner}/{repo}/compare/main...$pr_branch" --jq '.behind_by'
If behind_by > 0, comment @dependabot rebase on the PR and wait for the rebased CI to pass before invoking merge-pr.sh. Do not use gh pr view --json baseRefOid for this — baseRefOid is the base branch's current SHA at query time, so it always equals origin/main and cannot detect a stale PR head.
MCP gotchas reference
- snake_case fields: responses use
is_resolved,submitted_at,head.sha,commit.committer.date. Not camelCase. - Pagination: cap
perPageto 100 on list methods. Use cursor pagination viaafterfor GraphQL. - Labels are full-replacement:
issue_write(method: "update", labels: [...])REPLACES the entire label set. Read current first. resolve_threadignores owner/repo/pullNumber: onlythreadIdmatters, but the schema requires the others.- Thread IDs:
PRRT_kwDOxxxformat fromget_review_commentsoutput.
Status token reference
Same as in scripts/workflow/AGENTS.md and emitted by merge-pr.sh, pr-watch.py, and _pr-gates.sh.
Cross-reference
- Spec:
docs/superpowers/specs/2026-05-16-pinpoint-pr-workflow-consolidation-design.md - Workflow scripts reference:
scripts/workflow/AGENTS.md - Subagent dispatch rules (N=1 strict, dispatch from main worktree): see
pinpoint-orchestratorskill