Contributions should be small, reviewable, and safe to discuss publicly.
A fleet node keeps a checkout that ccc-self-update.sh updates on a schedule —
the path recorded in ~/.claude/self-update.repo, typically /opt/ccc-node,
$HOME/ccc-node, or /root/ccc-node. Keep it on main. Do not develop in
it.
The updater is fail-closed: it refuses to run unless the checkout is on main
with a clean tree. So the moment you branch or leave an edit there, that node
stops updating — not until the next tick, but until a human puts it back
(#1039). Three mechanical guards back this up (#1328, #1397): setup.sh
installs the managed-checkout guard as a post-checkout hook that warns AT THE
MOMENT you switch off main and as a pre-commit hook that REFUSES commits
while off main (CCC_MANAGED_CHECKOUT_GUARD=warn makes it advisory, =0
silences both); and the updater auto-recovers a wrong-branch stall whenever
the tree is clean — a fully pushed stray branch switches straight back, an
unpushed one is pinned under refs/ccc-stray/<branch>/<utc-ts> first so no
commit can be orphaned, and the notice tells you where it lives. A dirty tree
or a main held by a linked worktree still require the human path below.
Develop in a separate worktree instead:
git -C /opt/ccc-node worktree add ~/dev/<slug> -b <type>/<slug> origin/mainThe managed checkout stays on main and keeps updating; git also refuses to
check out the same branch twice, which enforces part of this for you. Two
things a worktree does not solve:
setup.shinstalls from its own location, so running it from a dev worktree installs unmerged code as the node's harness. Run it only from the managed checkout.- Reverting a stray branch is itself a repo mutation. The updater's
auto-recovery (#1328) only fires on the provably lossless shape and only
after the bridge's idle gate; a MANUAL
git checkoutbypasses that protection. Check the bridge's idle gate (~/.telegram_bot/health.json,workload.active_requests) first — the updater defers while the bridge is busy precisely because swapping the tree under a running session destroys in-flight work.
Multiple workers — human and agent nodes alike — pull from the same issue backlog, and nothing else coordinates who implements what. On 2026-08-18 the same #1081 piece was independently implemented twice and the PRs opened 46 seconds apart (#1141, #1142); the second implementation, hours of work with green CI, was closed unmerged (#1143 records the measurements). A design comment on an issue is not a reservation: both implementations started from the same design comment, each reading it as "ready for anyone."
So make the reservation explicit before you start implementing:
- Claim first. Self-assign the issue (preferred), or leave a start comment stating the scope you are taking and roughly when. Do this before branching, not when opening the PR — the PR is hours too late.
- Respect existing claims. If an issue has an assignee or a live start comment, do not begin a competing implementation. Reviewing, commenting, and designing stay open to everyone.
- Release what you drop. Un-assign or comment when you stop. A claim with no linked branch or PR after 7 days can be treated as released.
This covers repository backlog work only. Lane work dispatched through the A2A broker already carries its own reservation (task claims), and does not need a second one here.
Explicit operator-approved behavior and acceptance criteria are requirements, not suggestions for a cleanup or security-review pass. Reviewers may harden the implementation while preserving those semantics, but must not invert defaults, opt-in/opt-out direction, or the approved operating model without a new, explicit operator decision. If a security concern appears to require such a policy change, stop and present the conflict instead of silently redesigning the change. Authority to tidy, review, approve, or merge a PR does not by itself authorize a product-policy reversal.
Before opening a pull request:
- Keep runtime credentials, local state, generated artifacts, private paths, and raw logs out of the diff.
- Add or update tests when behavior changes.
- Run the repository's documented checks where practical.
- State whether the change is source-only.
Useful local checks:
bash scripts/validate-harness.sh
ruff check .
mypy
cd bridge && python -m pytest -qThe following actions remain separate approval gates and must not be bundled into ordinary contribution PRs: visibility changes, release/tag/package publish, production deploy/restart/reload, database mutation, provider/Telegram live sends, credential movement, force-push/history rewrite, or other destructive operations.
- Version tags use
v0.MINOR.PATCHuntil the harness reaches a stable 1.0 contract. Use MINOR for user-visible features/behavior changes and PATCH for fixes, docs, and tooling-only bundles. - Cut releases in trains, not on every merge. Prefer tagging after a meaningful issue bundle lands, with a practical upper bound of one release train per week.
- Before tagging, move completed notes from
CHANGELOG.mdUnreleasedinto a dated version section, run the local checks above, and verifyscripts/ccc-version.shresolves the intended tag aftergit fetch --tags. - Creating/pushing tags and GitHub Releases is a separate release approval gate; do not do it as part of a normal PR without explicit operator approval.