Forking
View as markdownIn the forking workflow contributors don't push to the canonical repository at all. They fork it, work on their own copy, and open a pull request from the fork. The maintainer's job is a review gate: triage what comes in, run it through checks, and merge only what clears sign-off. It's the model most open-source projects run.
Shelbi models the maintainer's side. Shelbi never fetches from a
fork, so you bring each contribution into your own checkout as a local
branch and name it in the issue's
branch:
field. The workflow is mostly about the gate: a handoff sign-off status
that's owner: user so nothing reaches main without a human accepting
it.
The board
A fork PR lands in Incoming, the orchestrator triages it into scope,
the reviewer agent runs it through checks in In Review, and it waits
in Sign-off for a maintainer to accept before it's Merged.
The statuses
The forking flow relabels the whole board. Do it by renaming the shipped
statuses in the project's
status catalog
(workflows/statuses.yaml) rather than replacing their ids. The shipped
task and subtask workflows reference these six ids, and a missing one
stops them from loading (and the whole project too, while task is its
default_workflow). The id
in-progress also matters on its own: Shelbi only hands finished work
forward automatically from a status with that id.
# workflows/statuses.yaml — the shipped ids, relabeled for the forking flow.
statuses:
- { id: backlog, name: Incoming, category: backlog }
- { id: todo, name: Triage, category: ready }
- { id: in-progress, name: In Review, category: active }
- { id: review, name: Sign-off, category: handoff }
- { id: done, name: Merged, category: done }
- { id: canceled, name: Declined, category: archived }The workflow
# workflows/contribution.yaml — reference-only; identity lives in statuses.yaml
name: contribution
description: Forking flow — review gate for pull requests from forks.
git:
base_branch: main
statuses:
- { id: backlog, owner: user } # Incoming
- { id: todo, owner: agent, agent: orchestrator } # Triage
- { id: in-progress, owner: agent, agent: reviewer } # In Review
- { id: review, owner: user } # Sign-off
- { id: done, owner: user } # Merged
- { id: canceled, owner: user } # Declined
initial_status: backlog
transitions:
- { from: in-progress, to: review, actions: [] } # no PR to open: the contributor already didSet default_workflow: contribution in project.yaml so new issues use
it (or put workflow: contribution in each issue's frontmatter).
The transitions: block is there to switch git side-effects off. A
workflow with no transitions: block at all gets the legacy hand-off,
which opens a PR. With the block, every edge that isn't listed with
actions is a pure status move. Git work on GitHub stays yours:
- No
open_pr. The contributor already opened the PR from their fork. The issue just tracks it. - No
mergeorclose_pr. Shelbi looks a PR up by the issue's branch name (gh pr list --head pr-401), which doesn't find the contributor's PR: its head is a branch on their fork. Merge or close the contributor's PR on GitHub, then move the card to Merged or Declined. With nomergeedge, Zen Mode never merges a contribution either. - No
delete_branch. The fork keeps its branch whether the PR merges or is declined.
To bring a PR in, fetch its head into the checkout Shelbi manages and point the issue at that branch:
git fetch origin pull/401/head:pr-401---
id: pr-401
title: "PR #401: perf: cache regex"
branch: pr-401
---If the branch doesn't exist locally when the issue is dispatched, Shelbi
cuts a fresh, empty branch of that name off main, and the reviewer has
nothing to review. When the reviewer hands off, Shelbi rebases pr-401
onto main and pushes it to origin under the same name, as it does for
every task branch, so the contribution also shows up as a branch in your
repo.
The In Review status names an agent: reviewer
role, an agent you author to run the untrusted contribution through the
project's checks and summarize risk before a human ever looks. Create it
first with shelbi agent new reviewer: the workflow won't load until the
agent exists. Or point the status at a shipped reviewer preset instead,
security or adversarial. Because it runs in in-progress, the
reviewer gets the same hand-off instructions as a developer: it writes the
ready marker when it's done, and Shelbi moves the card to Sign-off.
The Sign-off status stays owner: user: the gate is the whole point.
Orchestrator adjustments
The review gate needs the orchestrator to treat incoming forks as untrusted and never to auto-accept. Encode a triage rule and a hard stop at sign-off:
## Routing rules — forking
- **Incoming fork PRs** are untrusted. Never dispatch a contribution to a
workspace that holds credentials or can reach production. Route `Triage`
cards only to sandboxed hub workspaces.
- In `Triage`, decline anything out of scope (wrong direction, duplicate,
no linked issue) into `Declined` with a one-line reason. Promote the
rest to `InReview` for the `reviewer` agent.
## Zen Mode — forking
The `SignOff → Merged` edge is `owner: user` and must stay a human
decision. Do **not** auto-merge contributions even when checks are green:
report "`<PR>` passed review and is ready to sign off" and wait. A green
check is necessary but not sufficient for merging someone else's code.That last rule is the difference between forking and trunk-based: trunk-based leans into auto-merge for your own green work; forking deliberately keeps a human at the gate for code that arrived from outside.
See also
- Agents — how to author the
reviewerrole theInReviewstatus dispatches to. - Workflows: the
branch:task field: pointing an issue at an existing local branch instead of cutting one. - Trunk-based — the opposite end of the auto-merge spectrum, for work you trust.