Thanks for helping out. One thing to understand before you start:
Every change here is live-fire. Other repositories call these workflows at pinned SHAs, and moving the
v1tag pushes your change to every caller pinned to the tag on their next run. There is no staging environment. A workflow that is merely wrong fails someone else's CI; one that is wrong about permissions fails it at startup with no logs to explain why.
Run the tests for whatever you touched. They are the same commands CI runs:
# groom (ledger + dedup)
python3 -m unittest discover -s .github/groom/tests -p 'test_*.py' -v
# cursor-review (finding extraction, bot identity)
python3 -m unittest discover -s .github/cursor-review/tests -p 'test_*.py' -v
# AGENTS.md standard checker
python3 -m unittest discover -s .github/agents-md-integrity/tests -p 'test_*.py' -v
# public-repo leak guard (allowlist + the caller-can't-edit-it assertions)
python3 -m unittest discover -s .github/public-repo-hygiene/tests -p 'test_*.py' -v
# caller-bump machinery
shellcheck -x .github/bump-callers/bump-callers.sh .github/bump-callers/tests/test_bump_callers.sh
bash .github/bump-callers/tests/test_bump_callers.sh
# PR-size classifier
cd scripts/check-pr-size && go vet ./... && go test ./...Do not commit build outputs. scripts/check-pr-size/ is compiled from source
by pr-size.yml at run time; a committed binary is dead weight (and the wrong
architecture for the ubuntu-latest runners).
Ask two questions in order:
1. Does this change what a caller must send or grant? Adding a required
input, adding a required secret, or making a nested job request a new
permission are all breaking, even though nothing in the caller's YAML
changed. GitHub validates the permission grant at startup, so a caller that
was fine yesterday fails with an opaque "workflow file issue" and zero jobs. If
the answer is yes, bump the major tag (v1 → v2) and let callers opt in.
2. Does it change assets loaded at run time? Several workflows fetch prompts,
briefs, or checker scripts from workflows_ref while running. That input is
required with no default (groom.yml excepted — it defaults to '' and every
asset checkout falls back to job.workflow_sha, the commit the caller's uses:
resolved to, so leaving it unset there is safe; see the README), so a caller that
bumps uses: and leaves an explicitly-set workflows_ref: behind mixes your new
workflow with its old assets. When you
change assets and workflow together, say so in the PR body so callers bump both.
- Add
.github/workflows/<descriptive-name>.ymlwithon: workflow_call:. Document every input and secret inline. - Declare minimum permissions per job, not at the workflow level. Callers must grant the union of what your nested jobs request — keep that union small and state it in the header comment.
- Add a setup guide at
docs/callers/<descriptive-name>.mdfollowing the shape of the existing ones: a complete, copy-pasteable caller (includingon:), the exact permission grant, required vs optional secrets andvars, and any footguns. A guide that omitson:or the permission grant is not a guide. - Add a one-line row to the README table linking to it.
- If the workflow should be adopted broadly, add a
<NAME>_CALLERSroster secret and abump-<name>-callers.ymljob so pins get bumped automatically. See .github/bump-callers/. - Add a
test-<name>.ymlif it ships scripts.
Two steps. Missing the second is the common mistake:
- Add the caller workflow to the consumer repo (see
docs/callers/). - Add the repo to the matching
<NAME>_CALLERSroster secret on this repo (jq -c . callers.json | gh secret set <NAME>_CALLERS --repo Comfy-Org/github-workflows, from the canonicalcallers.jsonin the private infra/ops repo — a secret, not a variable, so caller names stay out of the public run logs, and there is no read-back). That roster is whatbump-*-callers.ymlreads to keep pins current. A repo absent from it keeps its original SHA forever, drifts behind the reusable, and eventually breaks when the two stop being compatible.
The AI workflows (cursor-review.yml, groom.yml) aim to split model execution
from credential use: the agent jobs run with contents: read and mint no GitHub
token, emitting a patch or findings that a separate job applies as a GitHub
App. Preserving that boundary matters more than convenience — do not give an agent
job a write token to save a step.
The split is clean for groom.yml and across the whole of cursor-review.yml:
every panel cell and the Consolidate panel judge job check out PR code and
hold contents: read only, and the review is posted from a separate Post review
job that checks out no PR code and receives its payload as an artifact. No job
both checks out PR code and holds a write-scoped credential. That is a structural
invariant, not a convention — .github/cursor-review/tests/test_workflow_job_isolation.py
fails the build if a checkout or a write grant crosses back over. See
SECURITY.md.
Reviewers and assignees are routed automatically from
.github/reviewers.yml by path. If you are changing a
bucket's globs, read the comments at the top of that file first — it explains why
the buckets are the durable part and the names are not.