-
Never write outside the repository. Worktrees go in
.claude/worktrees/<pkg>, scratch files in.git/pw-scratch/<pkg>, local lock state in.git/pw-locks/. No files in$HOME,~/.local/bin,/tmp, or sibling directories, and no installing software on the host (brew/apt/dnf/npm/pip). If you think you need either, ask first. -
A port adds files only under
.github/workflows/andpatches/<pkg>/<version>/. Never create aci/directory, and never add a helper script, Dockerfile, or test file anywhere else in the repo — not for a build step, not for a smoke test, not "just this once" because the content is long. Anything a job needs that is not a patch is written by the workflow at run time from arun:heredoc (gotcha 7): into$RUNNER_TEMPfor a docker build context, or into the upstream checkout for something cibuildwheel must carry into the container ({project}/<name>then names it). This has been asked for three times —ci/memray,ci/pyogrio,ci/pyroscope-iowere each removed after the fact — so treat a new top-level path as a hard stop, not a judgement call. If a file genuinely cannot be inlined, ask before writing it.- Reproduce the file byte for byte when inlining: quote the heredoc marker (
<<'EOF') so nothing expands, and diff the extracted block against the original before pushing. cat >drops the executable bit, so a script invoked by path needschmod +x; one invoked asbash <script>does not. Getting this wrong costs a full image build.
- Reproduce the file byte for byte when inlining: quote the heredoc marker (
-
Commit identity is
Ludovic Henry <git@ludovic.dev>and is already configured. Never pass-c user.email/-c user.nameor setGIT_AUTHOR_*/GIT_COMMITTER_*— in particular do not use the user's address from your own session context, which is a different address. Apre-commithook rejects any other identity (and any workflow addingBUILD_VERBOSITY); if it fires, fix the command, don't bypass the hook.ci_scripts/git-identity.shis for CI, not for you — never run it. It setsuser.name/user.emailtogithub-actions[bot](or the App bot whenAPP_SLUGis set) so workflow commits are attributed to the bot. Run locally it silently rewrites.git/config, and becausegit configin a worktree writes the common config, it clobbers the identity for the shared checkout and every other worktree/agent too — the next commit anywhere lands asgithub-actions[bot]. Read it if you need to know what CI does; restore withgit config user.name "Ludovic Henry"andgit config user.email "git@ludovic.dev", and checkgit log -1 --format='%an <%ae>'before pushing. Thepre-commithook is not always installed locally, so nothing else catches it.
-
Bookkeeping commits (
.queue.yml, a new gotcha, aci_scripts/fix) go straight toorigin/main, never a feature branch — but many agents do this concurrently, so useci_scripts/safe_push_main.shinstead of a baregit push. It always pushesHEAD(a baregit push origin mainpushes your localmainbranch, which may be stale, if you aren't literally on a branch namedmain), fetches and rebases onto the latestorigin/mainfirst, retries on a race with another agent's concurrent push, and refuses to push if.queue.yml's package count would shrink (a signal you rebased onto a stale snapshot and are about to silently revert someone else's already-landed commit — this has nearly happened more than once). If it reports a real rebase conflict, resolve it by hand before re-running it. Before writing a new gotcha number, runci_scripts/next_gotcha.sh(and again right before the final push) — concurrent agents have repeatedly collided on the same next number. -
A single commit must never mix a package port (
.github/workflows/,docs/packages/<pkg>.yaml,patches/<pkg>/<version>/) with bookkeeping (skills/,ci_scripts/,.queue.yml, anything else) — they go to different places (the port to its own PR branch, bookkeeping straight tomainper the bullet above), so a commit that mixes them puts the wrong half wherever it lands. This has been violated repeatedly: an agent researching a port legitimately finds a new gotcha or writes a helper script, and it rides along in the same commit as the port itself. Runci_scripts/check_port_pr_scope.sh --branchbefore your final push to catch it across a whole branch's commits. Better: install it as a local pre-commit hook once per clone so every future commit is checked as you make it —git config core.hooksPath ci_scripts/git-hooks(this setting lives in.git/config, not the tracked tree, so it does need setting again in a clone that doesn't have it yet;git config --get core.hooksPathshows whether it's already set — but it is shared across all worktrees of one clone once set, main checkout included). The check is about what a commit's diff contains, not what branch you're on — a bookkeeping-only commit passes from any branch, including a worktree that can never literally check outmainitself. One important limitation: a worktree's hook runs whateverci_scripts/check_port_pr_scope.shis actually committed in that worktree's own checkout — if the script itself has a fix onmainthat this worktree hasn't picked up yet (it was created before the fix, or hasn't merged since), the hook still runs the old logic until that worktree syncs withmain. -
No commit message may mention Claude or Anthropic, in any form (
Co-Authored-By: Claude ...,Claude-Session:,Generated by Claude Code, etc.) — this has leaked through twice from a tool's own auto-appended trailer, once all the way into a merged PR (#2151), because the only thing checked was the PR body/comment footer, not the commit message itself. The samecore.hooksPathinstall above also wires up acommit-msghook (rejects it before the commit is even made) and apre-pushhook (rescans every commit about to be pushed, in case one was made before the hook was installed or with--no-verify) — both callci_scripts/check_no_ai_attribution.sh.pr-checks.ymlruns the same script over every commit in a PR as a second, clone-independent net, and separately over the PR's own title and body — acreate_pull_requesttool call can append its own "Generated by ..." footer to the description without touching any commit message at all (a real leak caught this way on the chalkpy-rs port, after the commit-message checks above were already all green), so the commit-scanning checks alone don't cover it. Editing the body after that check has already run does not clear a failure by itself —pr-checks.ymltriggers oneditedfor exactly this reason (title/body edits alone now re-run it against the fixed text), so re-running the same job or leaving the trigger at its default[opened, synchronize, reopened]would otherwise replay the stale payload and fail again even after the footer is gone; two ports (pyvrl, certbot-dns-multi) worked around this by closing and reopening their own PR before theeditedtrigger existed — don't do that, a body edit now retriggers the check on its own. None of this touches file/diff content, only commit messages and the PR title/body: a patch can legitimately carry a real person named Claude as an upstream byline, and there is no way to tell that apart from an AI attribution trailer from content alone. -
The docker daemon is shared with every other agent on this host. Never stop or kill containers by an image filter —
docker ps -q --filter ancestor=quay.io/pypa/manylinux_2_39_riscv64 | xargs docker killmatches every concurrent port's local rehearsal, because they all run in the same handful of manylinux/musllinux images. One such sweep killed a sibling agent'scibuildwheel-*container mid-rehearsal, and there is no way to restart someone else's job. Start your own with an explicit--name <pkg>-<what>, stop only that name, and readdocker ps --format '{{.Names}}'before any cleanup. The same applies todocker system pruneand to pulling: an image another agent is mid-pull on is shared state too. -
Pushing workflow files needs
workflowscope on the gh token, else the push is rejected ("refusing to allow an OAuth App to create or update workflow … withoutworkflowscope"). Fix:gh auth refresh -h github.com -s workflow(interactive). -
origin(riseproject-dev/python-wheels) is canonical; there is no separateupstreamremote. Branch fromorigin/main. -
Use
ghextensively for anything requiring access to GitHub.