You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(release): inline literal sibling-repo paths in gaia-release pushes
repo-scope.sh's cmd_targets_foreign_repo() reads tool_input.command
verbatim and cannot expand shell variables. A `git -C "$CG" push` /
`git -C "$WEB" push origin main` resolves the target to the literal
string $CG/$WEB, the path lookup fails, the guard fails closed
(enforce), and block-main-destructive-git.sh denies the legitimate
sibling push as a home-repo push to main. Only literal paths resolve.
Steps 13-14 now discover the sibling path, print its canonical
absolute path, and instruct the runbook to inline that literal into
every sibling `git -C … push` (using a portable /abs/path/to/<repo>
placeholder) rather than passing $CG/$WEB. Corrects the prior claim
that a `-C "$CG"` push is recognized as foreign — only the
`gh pr merge -R owner/repo` slug form (basename compare) is var-safe.
Adds a bats case documenting the limitation: a literal `$CG` token
classifies as NOT foreign (return 1, enforce).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Commit on a branch, open + merge a PR, then tag. The PR-merge and main-push guards are repo-scoped (`.claude/hooks/lib/repo-scope.sh`): a `gh pr merge` / `git push` carrying a `-C "$CG"` or `cd "$CG" &&` prefix targets the sibling repo, so this repo's audit gate and main-protection do **not**fire — no manual-UI detour needed. `create-gaia` has no audit infrastructure or branch protection of its own; a plain `--merge` (not `--auto`) is correct there.
169
+
Commit on a branch, open + merge a PR, then tag. The PR-merge and main-push guards are repo-scoped (`.claude/hooks/lib/repo-scope.sh`), but the two surfaces resolve the sibling differently. `gh pr merge -R gaia-react/create-gaia` is recognized as foreign by repo-**name** (basename) comparison, so this repo's audit gate does **not** fire — no manual-UI detour needed. Raw-git operations (`git -C <path>`, `cd <path> &&`) are recognized as foreign only by resolving the **filesystem path** from the raw command string: `repo-scope.sh` reads `tool_input.command` verbatim and cannot expand shell variables, so a `$CG` form fails to resolve, the guard falls back to enforcing home-repo main-protection, and a legitimate sibling push is denied. Every sibling `git -C … push` below therefore inlines the **literal absolute path**the discovery step printed, never `$CG`. `create-gaia` has no audit infrastructure or branch protection of its own; a plain `--merge` (not `--auto`) is correct there.
168
170
169
171
> [!important] Sibling-repo push protocol
170
-
> `repo-scope.sh` uses a single-capture regex and **fails closed (enforces home-repo policy) when it sees more than one `-C` in a single command string** — git's last-wins semantics defeat a single capture. A chain like `git -C "$CG" add ...; git -C "$CG" commit ...; git -C "$CG" push origin main` therefore triggers the local main-push deny even though the push targets the sibling repo. Each `git -C "$CG" push ...` (branch push **and** tag push) must run in **its own Bash tool invocation** — one `-C` per call. Non-push `-C` chains (add, commit, fetch, checkout, pull, tag without push) are fine to combine.
172
+
> `repo-scope.sh` classifies a raw-git command as foreign only when it can resolve a concrete target path from the **raw command string**. Two things defeat that, and both make the guard fail closed and deny a legitimate sibling push:
173
+
> 1.**Variables don't expand.** The hook reads `tool_input.command` verbatim. `git -C "$CG" push …` resolves the target to the literal string `$CG`, the path lookup fails, and the guard enforces home-repo policy. **Inline the literal absolute path** the discovery step printed (e.g. `git -C /abs/path/to/create-gaia push …`), never `$CG`.
174
+
> 2.**One `-C` per push invocation.** The guard uses a single-capture regex and fails closed when it sees more than one `-C` in a single command string (git's last-wins semantics defeat a single capture). Each push (branch push **and** tag push) runs in **its own Bash tool invocation** — one `-C`, literal path.
175
+
>
176
+
> Non-push `-C` chains (add, commit, fetch, checkout, pull, tag without push) keep the `$CG`/`$WEB` variable and combine freely — only pushes (and force-pushes) need the literal-path, one-`-C` treatment.
Branch push — own Bash invocation, **literal path inlined** (substitute the path printed by the discovery step for the placeholder; do not pass `$CG`):
Commit and push directly to `main` in the website repo (no branch protection on `website`). Apply the sibling-repo push protocol from Step 13 — the `git -C "$WEB" push`must run in its own Bash tool invocation, separate from the `add`/`commit` chain:
248
+
Commit and push directly to `main` in the website repo (no branch protection on `website`). Apply the sibling-repo push protocol from Step 13: the `add`/`commit` chain keeps `$WEB` and combines freely, but the `git push`runs in its **own Bash tool invocation** with the **literal path inlined**. A `git -C "$WEB" push origin main` resolves the target to the literal string `$WEB`, fails closed, and is denied as a home-repo push to `main`.
0 commit comments