Breaking change. The monorepo's gitea host was previously inferred from the first target's subrepo_url, silently coupling the two and breaking on any multi-host setup (monorepo on host A, target on host B). v2.0.0 replaces the inference with a required josh.monorepo_url field that mirrors subrepo_url — same parsing, same SSH/HTTPS auth options, same shape. - schema_version: 2 is now required; v1 configs are rejected with a clear migration error - josh.monorepo_url is required; josh.monorepo_auth is optional (default https) - mono_auth_url() in lib/auth.sh mirrors subrepo_auth_url() - MONOREPO_API derives from monorepo_url's host instead of .[0].gitea_host; env-var override preserved as escape hatch - initial_import() and force-push call mono_auth_url() instead of building URLs inline - ADR-012 documents the rationale; CHANGELOG includes migration snippet Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.9 KiB
ADR-012: Explicit monorepo_url (drop first-target-host inference)
Status: Accepted Date: 2026-04
Context
josh-sync v1.x had no first-class field for the monorepo's git location. To call the gitea PR API and to clone the monorepo for initial_import / force-push, the script derived the gitea host from the first target's subrepo_url:
# lib/config.sh (v1.x)
gitea_host=$(echo "$JOSH_SYNC_TARGETS" | jq -r '.[0].gitea_host')
export MONOREPO_API="${MONOREPO_API:-https://${gitea_host}/api/v1/repos/${MONOREPO_PATH}}"
This worked silently for every existing config — studio, kinanah/stores, itkan/sdlc — because in each, target 0's host happens to match the monorepo's host. The portal monorepo broke the assumption: monorepo on code.atome.ac:atome-portal/portal, single target bff on code.itkan.io:sdlc/odoo_bff_core. The derivation produced https://code.itkan.io/atome-portal/portal.git (host borrowed from the wrong target), giving a 404 on josh-sync import.
Two issues stack:
- Implicit coupling: target ordering becomes load-bearing in a non-obvious way. The
MONOREPO_APIenv var was the only escape hatch, hidden in shell init. - Asymmetry:
subrepo_urlis declared explicitly per target with full SSH/HTTPS support; the monorepo had only amonorepo_pathand was always cloned via HTTPS+token. Local users with SSH keys for the monorepo still had to provide a token.
Alternatives considered
- Keep deriving, document the env override loudly. Cheapest. Leaves the booby trap in place — still fails closed for any future cross-host config.
- Add
josh.gitea_hostonly. Fixes the host issue without enabling SSH cloning of the monorepo. Half a fix, and asymmetric with how subrepos are declared. - Add
josh.monorepo_url(full URL), mirrorsubrepo_urlexactly. Removes the inference; enables SSH clone; symmetric with subrepos. Breaking — every existing config needs to add the field.
Decision
Add a required josh.monorepo_url field plus an optional josh.monorepo_auth (default "https"). Mirror the subrepo_url parsing and subrepo_auth_url() helper exactly:
parse_configextractsMONOREPO_GITEA_HOSTandMONOREPO_REPO_PATHfrommonorepo_urlusing the same jq regex assubrepo_url.MONOREPO_APIderives fromMONOREPO_GITEA_HOSTinstead of.[0].gitea_host. The env-var override stays as a real escape hatch (custom API endpoints).mono_auth_url()inlib/auth.shmirrorssubrepo_auth_url(): SSH returns the bare URL, HTTPS injects${BOT_USER}:${GITEA_TOKEN}@(auto-convertinggit@host:org/repo.gittohttps://host/org/repo.gitfirst).initial_import()and the force-push path inlib/sync.shcallmono_auth_url()instead of constructing the URL inline fromMONOREPO_API.
The change is shipped as v2.0.0 with schema_version: 2 required. v1 configs are rejected with a migration pointer; configs missing monorepo_url are rejected with the field name and an example.
Consequences
Positive:
- No more first-target-ordering coupling. Add or reorder targets freely.
- Multi-host setups (monorepo on host A, subrepo on host B) are first-class.
- SSH clone of the monorepo works without a token (the user's ssh-agent /
~/.ssh/configresolves the key). PR creation still needsGITEA_TOKEN. - Symmetric config: monorepo and subrepos both declare a URL + auth method.
Negative:
- Breaking change. Every existing config must add
schema_version: 2andjosh.monorepo_url. Mitigated by the validationdies pointing at the field name and changelog. - Two related-but-distinct fields (
monorepo_pathfor the josh-proxy filter;monorepo_urlfor the gitea clone) — kept separate because josh-proxy's path may not always equal the gitea path in custom setups.
Risks:
- Users may set
monorepo_urlto a host that doesn't match theirproxy_url's upstream. Detection is out of scope for this ADR; a follow-upjosh-sync preflightcheck could compare hosts and warn.