Contributor Onboarding Runbook
What it is
GiveLink has two roles: Architect (Dustin) and Contributor (non-developer team members who execute planned GSD phases under guardrails). The system is defined in ADR-0044 and documented as a parallel maintenance system in How It's Maintained.
This runbook walks through the operational procedures: how to set up the repo for contributors (one-time), how to onboard a new person, how to assign them work, how they execute it day to day, how the architect reviews and merges, and how to handle blocked work.
If you're the contributor, the only documents you need are CONTRIBUTING.md at the repo root (setup guide) and the Contributor Handbook in this catalog (daily workflow, testing, safety rails, and more). This runbook is mostly for the architect.
Prerequisites
For the architect, one-time:
- Admin access to the
datawake-dev/givelinkGitHub repo ghCLI installed and authenticated asdatawake-devorg admin- Local clone of the repo with
pnpm installalready run - The contributor's GitHub username and a Slack handle to reach them on
For each contributor, one-time:
- A GitHub account, added to the
datawake-devorg with read+write on the givelink repo (NOT admin — admin would let them bypass branch protection) - Claude Code OR Codex installed on their computer (not both required; Claude Code has stronger guardrails)
- Node.js 22 + pnpm installed (install via Volta or nvm —
CONTRIBUTING.mdwalks them through it) - Their copy of
.env.localwith the team's local-dev API keys (Stripe test keys, Clerk dev keys, demo Neon branch URL)
Procedure 1 — One-time repo setup (architect only, do this once per repo)
You only run this once, ever, for the whole repo. After it's done, every future contributor benefits automatically.
1a. Enable branch protection on main
This is the hard backstop that makes the whole contributor system safe. Without it, the local hook is the only defense and a contributor can bypass it with unset GIVELINK_ROLE.
- Open
https://github.com/datawake-dev/givelink/settings/branchesin your browser - Click Add branch protection rule (or Add classic branch protection rule)
- Set Branch name pattern to
main - Check these boxes:
- Require a pull request before merging → Required approvals: 1
- Require status checks to pass before merging → search for and select:
Lint, Type Check, Test & Build,review,EventCatalog (lint, sync, build),catalog-update - Require branches to be up to date before merging
- Do not allow bypassing the above settings (this is what stops admins from skipping it accidentally)
- Restrict who can push to matching branches → leave empty so only PR merges land on main
- Click Create (or Save changes)
- Verify by trying to push directly to main from your terminal:
git push origin mainshould be rejected. (You can still merge PRs because the merge happens through the GitHub UI /gh pr merge.)
Note: If you ever need to merge a PR despite the gates (e.g. solo work, no second reviewer), use
gh pr merge --admin --squash --delete-branch. The--adminflag is your override; use it sparingly and only when you understand what you're skipping.
1b. Verify the contributor system is in place
After PR #188 was merged, these files exist on main. If any are missing, the system isn't fully installed:
ls .claude/hooks/contributor-guard.sh
ls .claude/rules/contributor.md
ls CONTRIBUTING.md
ls .github/ISSUE_TEMPLATE/{phase-task,bug-report,refinement,config}.yml
ls scripts/assign-phase.ts
grep -q "## Contributor Role" AGENTS.md && echo "AGENTS.md has Contributor Role section"
grep -q "## Contributor checklist" .github/pull_request_template.md && echo "PR template has Contributor checklist"All seven checks should succeed. If any fail, the contributor system isn't fully merged — investigate before onboarding anyone.
1c. Verify the hook works
# Hook should be a no-op for the architect
unset GIVELINK_ROLE
echo '{"file_path":"src/lib/db/schema/donors.ts"}' | bash .claude/hooks/contributor-guard.sh
echo "exit=$?" # expect: exit=0
# Hook should block schema edits for a contributor
GIVELINK_ROLE=contributor echo '{"file_path":"src/lib/db/schema/donors.ts"}' \
| bash .claude/hooks/contributor-guard.sh
echo "exit=$?" # expect: BLOCKED message + exit=2If both checks pass, the hook is wired up correctly.
Procedure 2 — Onboarding a new contributor
You run this once per person, the first time they join.
2a. GitHub access
# Replace <username> with the contributor's GitHub username
gh api -X PUT "/orgs/datawake-dev/memberships/<username>" -f role=member
gh api -X PUT "/repos/datawake-dev/givelink/collaborators/<username>" -f permission=pushThe permission=push is critical — that's read+write but NOT admin. Admin would let them bypass branch protection.
Verify in the browser at https://github.com/datawake-dev/givelink/settings/access — they should appear as a collaborator with Write access.
2b. Send them three things in Slack
Paste this into a DM to the new contributor:
Welcome to the GiveLink team. Before we give you your first task, set up your computer:
1. Read the onboarding guide:
https://github.com/datawake-dev/givelink/blob/main/CONTRIBUTING.md
2. Install Claude Code (preferred) or Codex:
https://claude.com/claude-code
3. Once Claude Code is open, type to it:
"Set up the GiveLink project on my computer following CONTRIBUTING.md"
Claude will walk you through cloning the repo, installing dependencies,
setting up environment variables, and activating your contributor role.
When you're done with the setup steps, ping me here and I'll assign
you your first task.
The CONTRIBUTING.md has every step in browser-first language. You don't need to walk them through it personally unless they get stuck.
2c. Send them the local .env.local
Don't post it in Slack channels — DM only. The file lives in 1Password under "GiveLink local dev .env.local" (or wherever the team stores secrets). Forward it to them, they save it to the project root as .env.local.
2d. Verify they completed setup
Ask them to send you the output of running this in their terminal:
echo "GIVELINK_ROLE=$GIVELINK_ROLE"
gh auth status
ls .env.local && echo ".env.local exists"Expected:
GIVELINK_ROLE=contributorgh auth statusshows them logged in as their GitHub user.env.local exists
If any of those are missing, point them at the relevant section of CONTRIBUTING.md.
Procedure 3 — Assigning a phase to a contributor (architect)
You run this every time you have a planned phase you want a contributor to execute. The phase must already be planned via /gsd:plan-phase and the GitHub issue must already exist (created automatically by /github-sync plan).
3a. Pick a phase that's safe to delegate
Not every phase is contributor-safe. Avoid delegating phases that need to:
- Modify the database schema (
src/lib/db/schema/) — the hook will block them - Touch payment infrastructure (
src/lib/stripe.ts,src/modules/stripe-connect/) — same - Touch Clerk auth (
src/proxy.ts) — same - Modify any per-module
AGENTS.md— read-only by convention
Phases that work well for contributors: UI tweaks, copy changes, test coverage, content updates, dashboard polish, mobile fixes, accessibility improvements.
3b. Run the assignment script
# Replace 46 with the actual phase number
# Replace github-username with the contributor's GitHub username
tsx scripts/assign-phase.ts --phase 46 --assignee github-usernameThe script:
- Looks up the existing GitHub issue for phase 46 (via
.planning/.github-sync.jsonor byphase:46label) - Reads the phase plan from
.planning/phases/46-*/PLAN.md(or the milestone equivalent) - Rewrites the issue body with a "Getting Started (for contributors)" section that tells them exactly what to type to Claude
- Assigns the issue to the GitHub user
- Adds
role:contributorandtype:tasklabels - Records the assignment in
.planning/.github-sync.jsonunderphase_assignments
If the script can't find the phase plan automatically, pass --plan-path explicitly:
tsx scripts/assign-phase.ts --phase 46 --assignee github-username \
--plan-path .planning/phases/46-test-coverage/46-01-PLAN.md3c. Notify the contributor
The script doesn't ping them — assignment notifications come via GitHub email. Reinforce in Slack:
You have a new assignment: https://github.com/datawake-dev/givelink/issues/<NNN>
Open Claude Code in the GiveLink folder and type: Work on issue #<NNN>
Ping me when you have a PR ready for review.
Procedure 4 — Working on an assigned issue (contributor side)
This is what the contributor does. It is not the architect's job to walk through these steps live every time — CONTRIBUTING.md and the issue body have everything they need. The procedure is here so the architect can debug if a contributor reports it isn't working.
4a. Find the assignment
In a browser, go to https://github.com/datawake-dev/givelink/issues/assigned/@me. Click the issue.
4b. Start work in Claude Code
Open Claude Code in the GiveLink project folder. Type to Claude:
Work on issue #<number>
Claude will:
- Pull main
- Create a branch named after the issue (e.g.
gsd/phase-46-test-coverage) - Read the issue body and the linked phase plan
- Read the relevant
src/modules/{module}/AGENTS.mdfor context - Start implementing
4c. Test in the browser
Open http://localhost:3006 in the browser. (If the dev server isn't running, tell Claude: Start the dev server.) Verify the changes look right. If anything is off, tell Claude what to fix in plain English with a screenshot if needed.
4d. Iterate until acceptance criteria are met
The acceptance criteria are checkboxes in the issue body. Don't ship until all of them are satisfied.
4e. Submit for review
Run pnpm test, pnpm lint, and pnpm docs:lint, then create a PR
Claude will run the checks, fix anything that fails, push the branch, and create a PR with Closes #<number> in the body. The PR template includes the Contributor checklist — Claude will fill it in.
4f. Address review comments
When the architect requests changes, type to Claude:
Address the review comments on PR #<NNN>
Claude reads the comments, makes the changes, and pushes to the same branch. Vercel auto-deploys an updated preview.
Procedure 5 — Reviewing and merging a contributor PR (architect)
You run this every time a contributor opens a PR.
5a. Quick triage
gh pr view <NNN>
gh pr diff <NNN> --color=always | lessLook for:
- Scope creep. The contributor should only have modified files relevant to the issue. If you see refactoring, formatting changes, or "drive-by improvements" outside the issue scope, request changes.
- Restricted files. The hook should have blocked these, but verify: the PR should NOT touch
src/lib/db/schema/,src/lib/stripe.ts,src/modules/stripe-connect/,src/proxy.ts,drizzle/, anyAGENTS.md, planning files, or hook config. If it does, something's wrong with the hook setup — investigate before merging. - Test coverage. New behavior should have tests.
- The Contributor checklist. All boxes should be checked.
5b. Open the Vercel preview
Click the Vercel preview URL in the PR. Test the change yourself in the browser. This is the most important step — code review catches structure problems, but only running the app catches "this still doesn't work."
5c. Approve or request changes
# Request changes
gh pr review <NNN> --request-changes --body "Comment on each issue, then a summary."
# Approve
gh pr review <NNN> --approve --body "Tested in preview, works as expected. Merging."5d. Merge
gh pr merge <NNN> --squash --delete-branchThis squash-merges to main, deletes the contributor's branch, and triggers Vercel production deploy. Verify the deploy succeeded:
gh api repos/datawake-dev/givelink/commits/$(git rev-parse origin/main)/status \
--jq '.statuses[] | {context, state}'Both Vercel – givelink and Vercel – event-catalog (if affected) should show success. If a deploy failed, check the Vercel dashboard and either revert or fix forward.
5e. Close the issue
gh pr merge --squash automatically closes the linked issue if the PR body had Closes #<NNN>. Verify:
gh issue view <NNN> --json state
# expect: {"state":"CLOSED"}Procedure 6 — Handling blocked work
When a contributor needs something the hook blocks (a schema change, a Stripe integration, an auth tweak), they should NOT try to work around the restriction. Instead, they file a blocked issue.
6a. The contributor files the issue
They go to https://github.com/datawake-dev/givelink/issues/new/choose, pick the Bug Report template, fill it in describing what they need and why, and add the blocked label manually. They link the original issue they were working on.
6b. The architect (you) handles it
When you see a blocked-labeled issue in your queue, decide:
- Quick unblock. If it's a one-line schema change you can do in 5 minutes, do it yourself, push the change, comment on the blocked issue with the commit hash, and tell the contributor in Slack they can resume.
- Substantial work. If it's a real feature that needs design, plan it as its own GSD phase. Close the blocked issue with a comment pointing at the new phase.
- Won't fix. If the contributor was trying to do something that shouldn't happen at all, close the issue with an explanation.
The contributor's original issue stays open in either case — they pick it back up after the unblock.
Troubleshooting
"The hook isn't blocking restricted edits"
Run the verification from Procedure 1c. If the hook is a no-op even with GIVELINK_ROLE=contributor, check:
# Is the env var actually set in the contributor's shell?
echo "GIVELINK_ROLE=$GIVELINK_ROLE"
# Is the hook executable?
ls -la .claude/hooks/contributor-guard.sh
# expect: -rwxr-xr-x
# Is it registered in settings.json?
grep -q "contributor-guard.sh" .claude/settings.json && echo "registered"If GIVELINK_ROLE is unset in the contributor's terminal, they didn't add export GIVELINK_ROLE=contributor to their shell profile, OR they added it but haven't restarted the terminal. Have them open a fresh terminal and re-check.
"The contributor pushed to main directly"
This shouldn't be possible if branch protection is on (Procedure 1a). If it happened, branch protection is misconfigured. Re-check the settings at https://github.com/datawake-dev/givelink/settings/branches.
"The assign-phase script can't find the issue"
The script looks up issues in two places, in order:
.planning/.github-sync.json→issues.{phase}.numbergh issue list --label "phase:{phase}" --state open
If neither finds the issue, run /github-sync plan first to create it, then re-run the assign script.
"The assign-phase script can't find the plan file"
The script auto-discovers plans under .planning/phases/{phase}-* and .planning/milestones/*-phases/{phase}-*. If your phase is somewhere else (or has a non-standard name), pass --plan-path explicitly.
"The contributor's PR is BEHIND main when they try to merge"
Branch protection requires up-to-date branches. The contributor (or you) needs to update the branch:
gh pr update-branch <NNN>Then re-trigger CI and merge once it's green.
"CI checks are 'expected' but never appear"
This usually means a workflow file references a check name that doesn't match what GitHub computes. Look at .github/workflows/ci.yml and claude.yml and verify the job names match what's listed in the branch protection settings. Update the protection settings to match the actual job names.
"I need to merge a contributor PR but the Claude Code review check failed and I want to merge anyway"
gh pr merge <NNN> --squash --delete-branch --adminUse --admin sparingly. The whole point of the review check is to catch real problems before they reach main. If you find yourself using --admin regularly, something's wrong with either the check or your merge discipline.
"The contributor accidentally edited a file they're supposed to be blocked from"
The hook is local-only. If they're using Codex (which doesn't honor Claude Code hooks), or if they unset GIVELINK_ROLE, the hook won't fire. Branch protection is the backstop — they still can't merge without your review.
If you see a restricted file in a contributor PR, request changes and ask them to revert that file. Don't merge until it's gone.
References
- ADR-0044 — Contributor role with hook-enforced guardrails — the architectural decision and trade-offs
- How It's Maintained → "Contributor onboarding (parallel system)" section — how this fits alongside catalog and customer-docs maintenance
CONTRIBUTING.md(repo root) — the contributor-facing onboarding guide. Read by both humans and AI agents.AGENTS.md(repo root) — the Codex equivalent ofCLAUDE.md. Has a## Contributor Rolesection with the same restrictions..claude/hooks/contributor-guard.sh— the hook source.claude/rules/contributor.md— auto-loaded soft guardrails for Claude Code contributorsscripts/assign-phase.ts— the phase handoff helper. Mirrors thespawnSyncargv-array pattern fromscripts/create-tutorial-issue.ts..github/pull_request_template.md— has the Contributor checklist section.github/ISSUE_TEMPLATE/phase-task.yml— issue template used byassign-phase.ts(indirectly — the script overwrites the body, but the template defines the human-creatable form)