FeaturesDevelopersDocsChangelog
hello@givelink.ai
GGiveLink
For NonprofitsPricingCompareBlogAbout
Join the Waitlist
← All runbooks

Contributor Onboarding

How to onboard a non-developer team member as a Contributor, assign them GSD phases, and review their PRs. The architect-side and contributor-side daily flows for the role system defined in ADR-0044.

Last reviewed April 7, 2026

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/givelink GitHub repo
  • gh CLI installed and authenticated as datawake-dev org admin
  • Local clone of the repo with pnpm install already 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-dev org 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.md walks them through it)
  • Their copy of .env.local with 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.

  1. Open https://github.com/datawake-dev/givelink/settings/branches in your browser
  2. Click Add branch protection rule (or Add classic branch protection rule)
  3. Set Branch name pattern to main
  4. 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
  5. Click Create (or Save changes)
  6. Verify by trying to push directly to main from your terminal: git push origin main should 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 --admin flag 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=2

If 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=push

The 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=contributor
  • gh auth status shows 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-username

The script:

  1. Looks up the existing GitHub issue for phase 46 (via .planning/.github-sync.json or by phase:46 label)
  2. Reads the phase plan from .planning/phases/46-*/PLAN.md (or the milestone equivalent)
  3. Rewrites the issue body with a "Getting Started (for contributors)" section that tells them exactly what to type to Claude
  4. Assigns the issue to the GitHub user
  5. Adds role:contributor and type:task labels
  6. Records the assignment in .planning/.github-sync.json under phase_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.md

3c. 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:

  1. Pull main
  2. Create a branch named after the issue (e.g. gsd/phase-46-test-coverage)
  3. Read the issue body and the linked phase plan
  4. Read the relevant src/modules/{module}/AGENTS.md for context
  5. 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 | less

Look 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/, any AGENTS.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-branch

This 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:

  1. .planning/.github-sync.json → issues.{phase}.number
  2. gh 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 --admin

Use --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 of CLAUDE.md. Has a ## Contributor Role section with the same restrictions.
  • .claude/hooks/contributor-guard.sh — the hook source
  • .claude/rules/contributor.md — auto-loaded soft guardrails for Claude Code contributors
  • scripts/assign-phase.ts — the phase handoff helper. Mirrors the spawnSync argv-array pattern from scripts/create-tutorial-issue.ts.
  • .github/pull_request_template.md — has the Contributor checklist section
  • .github/ISSUE_TEMPLATE/phase-task.yml — issue template used by assign-phase.ts (indirectly — the script overwrites the body, but the template defines the human-creatable form)
GiveLink

Generosity, elevated.

Product

FeaturesFor NonprofitsPricingChangelogBlogDocsDevelopersRoadmap

Compare

GiveLink vs ZeffyGiveLink vs GivebutterGiveLink vs GoFundMe ProGiveLink vs Fundraise UpGiveLink vs DonorboxGiveLink vs Bloomerang

Company

About

Legal

PrivacyTermsAcceptable UseCookiesFee DisclosureTrademark

Our standing commitments

  • 1.No hidden fees. Any amount the donor is asked to cover is shown with a full breakdown — platform fee and processing fee itemized — before they submit.
  • 2.Every dollar you intended reaches your nonprofit. The fees you see on this page are the fees you pay — no tips, no surprise markup.
  • 3.We never call our fee a “tip” or a “contribution.” It's a fee. We name it. We show the number.

© 2026 GiveLink. All rights reserved.