Cross-repo changes
Some backlog items need a change in more than one repository — an image-tag bump, a Helm value, an ArgoCD manifest, or a Terraform file in a sibling config repo. Echo supports this through supporting repos, and it treats any cross-repo change as an automatic hold for a human merge.
Declaring supporting repos
Section titled “Declaring supporting repos”A project’s supportingRepos config lists the repos a worker is allowed to
touch. Each entry names the repo slug and, optionally, a local clone to prefer:
"supportingRepos": [ { "name": "infra", "repo": "your-org/your-infra-config", "localPath": "/home/you/projects/your-infra-config" }]The list defaults to [] — most projects never touch a supporting repo, and the
worker prompt’s cross-repo section is a no-op for them. The rendered list is
injected into the worker prompt so the worker knows exactly
which repos are in bounds.
Checking one out
Section titled “Checking one out”When Step 5 execution demonstrably requires editing a declared repo, the worker
runs checkout_supporting_repo.sh, which lazily creates an isolated worktree:
DEST="$(bash .../echo-core/scripts/checkout_supporting_repo.sh \ --project "$ROOT" --name infra --issue 42)"cd "$DEST"-
It resolves the source: explicit
localPathfirst, then the sibling-directory convention (../<repo-basename>next to the project root), then a remotegh repo clonefallback. -
It creates a worktree at
<worktree_base>/issue-<N>/<name>on branch<branchPrefix><N>(with an optional-<slug>suffix). The checkout is idempotent — a healthy existing worktree on the expected branch is handed straight back; an unhealthy one (wrong branch, not a git dir) is pruned and re-created. -
It prints the worktree path on stdout. The worker edits, commits, and pushes there with the same discipline as its main worktree — no
--no-verify, no amend.
The worker then opens the sibling PR with Refs <board-repo>#<N> — never
Closes. The Closes keyword only auto-closes within the issue’s own repo; in
a sibling repo it silently does nothing, leaving the issue open forever with no
error anywhere. The main-repo PR body lists every sibling PR under a
## Cross-repo PRs (merge first) heading so a human sees the merge order.
The cross-repo sentinel
Section titled “The cross-repo sentinel”On every successful checkout, checkout_supporting_repo.sh drops an
issue-scoped sentinel file:
<state_dir>/cross-repo-touched.<issue>This is the mechanism that forces a human merge. It’s written on the idempotent
short-circuit path and every fresh-checkout success path, and its write is
strictly best-effort (|| true) — a sentinel-write failure never fails the
checkout.
The forced human-merge hold
Section titled “The forced human-merge hold”The sentinel is the first deterministic gate in
gated_merge.sh. Before any other gate, it checks:
if [[ -f "$STATE_DIR/cross-repo-touched.$num" ]]; then hold "cross-repo change — human merge required (PR #$pr + sibling repo PR(s))"fiSo a cross-repo item is never auto-merged, regardless of merge.mode or any
review verdict. The worker’s job is unchanged from any other run: open every PR
(main + sibling), list the siblings under ## Cross-repo PRs (merge first),
label the main PR echo:merge-ready, and stop. It merges nothing — it has no
merge command. A human then merges the sibling PR(s) first, in the listed order,
then the main PR.
See also
Section titled “See also”- Merge gates — the full gate order the sentinel leads.
- The run lifecycle — where the worker opens PRs and stops.
- Config reference —
supportingRepos,branchPrefix.