git:20260711.b93dd0a to git:20260712.17384af

1 added, 1 removed. Audit A to A.

---
# SPDX-License-Identifier: Apache-2.0
# https://www.apache.org/licenses/LICENSE-2.0
name: magpie-setup-isolated-setup-install
family: setup
mode: Meta
description: |
Guide an adopter through the first-time install of the
framework's secure agent setup (bubblewrap + socat +
claude-code, sandbox/permissions/clean-env layers). Walks
every step interactively; never auto-runs sudo, shell-rc
edits, or settings overwrites.
when_to_use: |
Invoke when the user says "set up the secure agent setup",
"first-time install of the secure config", "install the
secure setup in this tracker", "walk me through the
secure-agent-setup install", or starts working on a fresh
adopter clone without secure-config wiring. Also appropriate
after a fresh OS install / new dev machine where
`~/.claude/scripts/` is empty. Skip when the secure setup is
already in place — use `setup-isolated-setup-verify` (to
confirm completeness) or `setup-isolated-setup-update` (to
refresh against the framework's latest) instead.
capability: capability:platform
license: Apache-2.0
---
<!-- Placeholder convention (see AGENTS.md#placeholder-convention-used-in-skill-files):
<project-config> → adopting project's `.apache-magpie/` directory
<tracker> → value of `tracker_repo:` in <project-config>/project.md
<upstream> → value of `upstream_repo:` in <project-config>/project.md -->
# setup-isolated-setup-install
This skill is the **on-ramp** for adopters who do not yet have the
secure setup running. It is a thin walkthrough wrapper around the
canonical install path documented in
[`docs/setup/secure-agent-setup.md`](../../docs/setup/secure-agent-setup.md). The
authoritative content lives there; this skill exists so an adopter
can say *"set up the secure agent setup"* in a fresh session and
land in the right step-by-step flow without first reading the
document.
## Adopter overrides
Before running the default behaviour documented
below, this skill consults
- [`.apache-magpie-overrides/setup-isolated-setup-install.md`](../../docs/setup/agentic-overrides.md)
+ [`.apache-magpie-local/setup-isolated-setup-install.md`](../../docs/setup/agentic-overrides.md) (personal, gitignored) and [`.apache-magpie-overrides/setup-isolated-setup-install.md`](../../docs/setup/agentic-overrides.md) (committed, project-wide)
in the adopter repo if it exists, and applies any
agent-readable overrides it finds. See
[`docs/setup/agentic-overrides.md`](../../docs/setup/agentic-overrides.md)
for the contract — what overrides may contain, hard
rules, the reconciliation flow on framework upgrade,
upstreaming guidance.
**Hard rule**: agents NEVER modify the snapshot under
`<adopter-repo>/.apache-magpie/`. Local modifications
go in the override file. Framework changes go via PR
to `apache/magpie`.
---
## Snapshot drift
Also at the top of every run, this skill compares the
gitignored `.apache-magpie.local.lock` (per-machine
fetch) against the committed `.apache-magpie.lock`
(the project pin). On mismatch the skill surfaces the
gap and proposes
[`/magpie-setup upgrade`](../setup/upgrade.md).
The proposal is non-blocking — the user may defer if
they want to run with the local snapshot for now. See
[`docs/setup/install-recipes.md` § Subsequent runs and drift detection](../../docs/setup/install-recipes.md#subsequent-runs-and-drift-detection)
for the full flow.
Drift severity:
- **method or URL differ** → ✗ full re-install needed.
- **ref differs** (project bumped tag, or `git-branch`
local is behind upstream tip) → ⚠ sync needed.
- **`svn-zip` SHA-512 mismatches the committed
anchor** → ✗ security-flagged; investigate before
upgrading.
---
## Golden rules
- **Do not auto-run privilege-elevating commands.** Anything that
needs `sudo` (apt / dnf installs, system-wide writes) is *printed*
for the user to copy-paste into their own terminal. The skill
never invokes `sudo` itself.
- **Do not edit shell rc files without approval.** `~/.bashrc` /
`~/.zshrc` modifications (sourcing `agent-iso.sh`, the optional
`alias claude='claude-iso'`) are surfaced as the exact line to
add; the user pastes it themselves. The skill confirms the rc
file path with the user first; it does not assume.
- **Do not overwrite an existing settings file silently.** If the
user already has a project `.claude/settings.json` or a
user-scope `~/.claude/settings.json`, the skill *diffs* the
desired merge against the existing file and asks for explicit
approval before writing. Re-installs / partial-state recoveries
are common — the skill must not blow away an unrelated
pre-existing hook or `permissions.ask` rule. The desired merge
**includes the agent-guard `hooks.PreToolUse` entry** (matcher
`Bash`, command running the user-scope `~/.claude/scripts/agent-guard.py`)
— the deterministic guard from
[`tools/agent-guard`](../../tools/agent-guard/README.md). Install
the script as `~/.claude/scripts/agent-guard.py` and populate
`~/.claude/scripts/guards.d/` from both the engine's bundled
`guards.d/*.py` and every skill-owned `skills/*/guards/*.py`
(so skill-owned guards like `mention` / `mark-ready` are active),
alongside the other user-scope scripts (Step P); wire the
`PreToolUse` entry once, and preserve any pre-existing `hooks`
entries.
- **Stop on the first failure.** If a step fails (manifest read
fails, framework path wrong, an existing file conflicts in a way
the user has not yet decided about), stop and report. Do not
push past a failure to the next step.
## Up-front confirmations
Before walking any install step, confirm with the user:
1. **OS / distro.** macOS, Ubuntu / Debian (apt), Fedora / RHEL
(dnf), or Arch / NixOS / other. macOS skips bubblewrap +
socat (Seatbelt is built-in); Linux installs both per the
distro shortcut.
2. **Framework checkout path.** The path to the user's local
`magpie` clone. Required to read
`tools/agent-isolation/pinned-versions.toml`,
`.claude/settings.json`, and the
`tools/agent-isolation/*.sh` scripts. If the user does not
have a clone, walk them through `git clone` first.
3. **Fresh install or re-install.** For a re-install on a partial
existing state, the skill must enumerate the existing wiring
(project settings.json, user settings.json, hooks dir, the
agent-guard `hooks.PreToolUse` entry + `~/.claude/scripts/agent-guard.py`,
shell rc) before any merge so the user knows what is being
preserved vs replaced.
4. **Sync repo (optional).** Whether the user maintains a
private dotfile-style `~/.claude-config` repo per
[Syncing user-scope config across machines](../../docs/setup/secure-agent-setup.md#syncing-user-scope-config-across-machines).
If yes, the skill installs user-scope scripts as **symlinks**
into `~/.claude-config/scripts/` rather than `cp`-ing into
`~/.claude/scripts/` — the symlink approach is what makes
sync push the upgrades to other machines automatically.
## Walk-through
Follow the canonical step list at
[docs/setup/secure-agent-setup.md → Adopter setup → Via a Claude Code prompt](../../docs/setup/secure-agent-setup.md#via-a-claude-code-prompt).
Each step in that list maps 1:1 to a step in this skill. Do not
re-write the list here — read the doc, follow it, and surface each
sub-step with the user. The doc names are the source of truth; the
skill is the runner.
For the verification step at the end, hand off to the
`setup-isolated-setup-verify` skill rather than re-walking the checklist
inline.
### Agent runtime — install `@latest`, enforce the floor
`claude-code` is **not** pinned to an exact version. Install it with
`npm install -g --no-save @anthropic-ai/claude-code@latest` (the
command in the doc's install list) — latest always carries the newest
permission-rule / sandbox / prompt-injection fixes. The manifest's
`[tools.claude-code]` table in
[`pinned-versions.toml`](../../tools/agent-isolation/pinned-versions.toml)
declares a `min_version` **floor**, not a pin. Because this install is
driven from Claude Code, apply the same hard gate
`setup-isolated-setup-verify` check 5 applies: read the running
version (`claude --version`) and, if it is **below** `min_version`,
**hard-fail** — stop the install, tell the operator to upgrade to
`@latest`, and have them re-run. The secure setup must not be stood up
on a below-floor runtime.
### Step P — Project-root coverage in the sandbox allowlists
Per
[`docs/setup/secure-agent-setup.md` → *Project-root coverage in the sandbox allowlists*](../../docs/setup/secure-agent-setup.md#project-root-coverage-in-the-sandbox-allowlists)
and [issue #197](https://github.com/apache/magpie/issues/197),
the harness pre-resolves `sandbox.filesystem.allowRead: ["."]` at
session start in a way that silently drops the literal `.` from the
resolved set, so a session in a freshly-cloned adopter repo can
write to CWD but cannot **read** from it under the sandbox.
The defensive measure is to add the project root as an explicit
**absolute path** to `sandbox.filesystem.allowRead` and `allowWrite`
in the adopter's **project-local** settings file
(`<repo>/.claude/settings.local.json`) — gitignored, per-machine,
per-project, merged on top of the committed project-scope and
user-scope by the harness. Worktrees handle themselves: each
worktree has its own `<worktree>/.claude/settings.local.json`,
and each gets its own root added.
#### Step P.0 — Ask the user: per-project or whole-user scope?
Before installing anything, ask the operator which scope they
want — see
[`docs/setup/secure-agent-setup.md` → *Per-project vs whole-user scope*](../../docs/setup/secure-agent-setup.md#per-project-vs-whole-user-scope)
for the full rationale + trade-offs:
- **Per-project.** The skill runs the helper for the
current project only. Future projects on this host need the
skill re-run in each, OR the operator can pick whole-user later.
No global git config changes.
- **Whole-user (global).** The skill walks the operator's existing
local git checkouts and populates their `settings.local.json`
files, then sets `git config --global core.hooksPath` so every
future `git checkout` / `git clone` / `git worktree add` on the
host picks up the framework's universal post-checkout hook.
**Detect whether whole-user (global) scope is already active first.**
Read `git config --global --get core.hooksPath`:
- If it is **unset** (or does not point at the framework's shared
hook dir), whole-user isolation is **not yet set up on this host**.
In that case **propose whole-user (global) as the default** — it
is the recommended baseline because it covers every current and
future repo on the host in one pass, so the operator does not have
to re-run this skill per project. Present per-project as the
narrower alternative for operators who deliberately want to keep
their per-repo git hooks (see the Step P.0a caveats).
- If it is **already set** to the framework's shared hook dir,
whole-user isolation is already in place; default to **per-project**
for this specific project (the global hook already covers the host,
so only this project's `settings.local.json` needs the local pass).
**Prefer structured Q&A.** When the agent harness offers a
structured-question tool (e.g. Claude Code's `AskUserQuestion`),
use a single-select prompt whose default is chosen by the detection
above — `Whole-user (global, recommended)` when whole-user is not
yet set up, otherwise `Per-project`. Free-form chat is the fallback.
If the user picks **per-project**, skip to *Step P.1 — Install
the helper script* and *Step P.2 — Run the helper for this project*,
then move on to the next step in the canonical install list.
If the user picks **whole-user**, follow Step P.0a's loud
disclosure first, then Step P.0b (pick the whole-user *flavour* —
simple vs dispatcher), then Step P.1, then Steps P.2-whole-user
(walk existing checkouts) and P.3-whole-user (install the global
hook + set `core.hooksPath`). If they chose the dispatcher flavour,
also run Step P.3b-whole-user (install the dispatcher + prek shim).
##### Step P.0a — Loud disclosure before setting whole-user scope
If the operator picked whole-user, surface this disclosure
**before** any global config write. The operator must
acknowledge it explicitly; no silent proceed:
> **!!! WHOLE-USER SCOPE — `core.hooksPath` GLOBAL OVERRIDE !!!**
>
> Setting `git config --global core.hooksPath` makes git look up
> hooks in **one shared directory** for every repo on this host.
> Every `.git/hooks/*` in every existing repo on this machine
> becomes **inert** — git will no longer fire your per-repo
> `pre-commit`, `commit-msg`, `pre-push`, or any other hook
> unless the shared dir chains back to them.
>
> There are two whole-user flavours (you choose next, Step P.0b):
>
> - **Simple** — the framework installs **only** the
> `post-checkout` hook in the shared dir. Every other per-repo
> hook stays inert until you migrate it into `~/.claude/git-hooks/`
> yourself. Pick this only if you don't rely on per-repo hooks.
> - **With per-repo dispatcher (recommended if you use prek /
> pre-commit / husky / any per-repo hook)** — the framework
> installs a **dispatcher** for each hook type. Each dispatcher
> runs the framework's own logic (the `post-checkout`
> sandbox-allowlist sync) **and then chains through to that
> repo's own `.git/hooks/<name>`**. Your per-repo hooks keep
> firing, and a repo with no hook is a clean no-op. A bundled
> `prek` PATH shim makes `prek install` write its shim into the
> repo-local `.git/hooks/` (via `--git-dir`) so the dispatcher
> can find it.
>
> Either way, whole-user scope is reversible:
> `git config --global --unset core.hooksPath` restores per-repo
> hook lookup, and (dispatcher flavour) removing
> `~/.claude/bin` from PATH restores the stock `prek`.
Confirm the operator wants to proceed with whole-user scope after
reading the disclosure. If they hesitate or pick per-project,
fall back to the per-project path.
##### Step P.0b — Choose the whole-user flavour: simple or dispatcher
If the operator confirmed whole-user, ask which flavour (see the
disclosure above for the trade-off). **Default to the dispatcher
flavour** — it is a strict superset of simple (it still runs the
`post-checkout` sync everywhere) and it avoids silently shadowing
per-repo hooks, which is the most common whole-user surprise.
Pick simple only if the operator explicitly wants the minimal
footprint and confirms they run no per-repo hooks.
**Prefer structured Q&A** here too: a single-select with
`With per-repo dispatcher (recommended)` as the default and
`Simple (post-checkout only)` as the alternative.
- **Simple** → Step P.3-whole-user installs `git-global-post-checkout.sh`
only.
- **Dispatcher** → Step P.3-whole-user still runs, and Step
P.3b-whole-user additionally installs `git-hook-dispatcher.sh`
(symlinked to each hook name, including `post-checkout` — which
in this flavour supersedes the standalone `git-global-post-checkout.sh`)
and the `prek` PATH shim.
#### Step P.1 — Install the helper script
(Both scopes.)
Copy `tools/agent-isolation/sandbox-add-project-root.sh` into
`~/.claude/scripts/sandbox-add-project-root.sh` (or symlink it
from `~/.claude-config/scripts/` if the operator uses the
private sync repo), mode `0755`. The script file lives
user-scope so a single install covers every adopter project on
the host; what it **writes** is project-local. The same install
mechanism the `sandbox-bypass-warn.sh` and `sandbox-status-line.sh`
helpers use (see the *Sandbox-bypass visibility hook* and
*Sandbox-state status line* sections of the doc).
#### Step P.2 — Run the helper for this project (per-project scope)
Skip if the operator picked whole-user scope (Step P.2-whole-user
below covers the equivalent).
Run the helper once with `--all-worktrees` in the adopter
repo's main checkout. The helper enumerates
`git worktree list --porcelain` and, for each worktree, writes
that worktree's absolute path into that worktree's own
`<worktree>/.claude/settings.local.json` (creating the file if
it does not yet exist). Idempotent, atomic, tolerant of missing
prereqs (see the script's header comment for the full
failure-mode list). On success, surface the diff so the operator
sees which entries landed; on no-op (paths already present),
surface a one-line "already covered" confirmation.
**Sandbox-bypass requirement when invoked from inside an agent
session.** `.claude/settings.local.json` is in Claude Code's
built-in sandbox `denyWithinAllow` set (verified empirically —
see
[`docs/setup/secure-agent-setup.md` → *Security rationale*](../../docs/setup/secure-agent-setup.md#security-rationale--why-project-local-is-safe-to-write-to)),
so the helper's Bash write is blocked when invoked through the
agent's `Bash` tool. If this skill is being walked from inside
a sandboxed session, invoke the helper with
`dangerouslyDisableSandbox: true` and the reason
*"writing project-local sandbox-allowlist entries (issue #197 fix)"*.
The bypass triggers `sandbox-bypass-warn.sh`'s loud-red banner
so the operator sees and approves the single write. When the
operator runs `setup-isolated-setup-install` directly from a
terminal (the typical first-time-install path), no bypass is
needed — the script runs outside the agent sandbox.
#### Step P.2-whole-user — Walk existing checkouts (whole-user scope)
Skip if the operator picked per-project scope.
Walk the operator's existing git checkouts and populate each
one's `.claude/settings.local.json`. This pass is **settings-only**
by default — it does NOT install per-repo `post-checkout` hooks
(the global hook installed in Step P.3-whole-user covers that
for both existing and future repos via `core.hooksPath`).
1. **Prompt the operator for root directories to scan.**
Default suggestions: `~/code/`, `~/projects/`, `~/dev/`,
`~/work/`. Show the operator the list, let them edit it.
Empty list → skip the walk; the operator can re-run this
skill later when they want existing repos covered.
2. **Walk each root dir.** Use a depth-limited `find` with
reasonable exclusions:
```bash
find "<root>" -maxdepth 5 -type d -name .git \
-not -path '*/node_modules/*' \
-not -path '*/.venv/*' \
-not -path '*/__pycache__/*' \
-not -path '*/build/*' \
-not -path '*/dist/*' \
-not -path '*/.cache/*' \
-prune
```
For each `.git/` found, the parent dir is a working tree.
De-duplicate by canonical path.
3. **For each working tree found, run the helper with
`--all-worktrees`.** Same invocation as the per-project
variant — the helper itself handles `git worktree list` so
linked worktrees of the same repo get processed. The helper's
built-in `git check-ignore` guard skips repos whose
`.claude/settings.local.json` is not gitignored (defense in
depth — the operator should fix the `.gitignore` first).
4. **Tabulate the result** for the operator: how many checkouts
were scanned, how many had `.claude/` (so the helper wrote),
how many were skipped (no `.claude/` directory, or
`.claude/settings.local.json` not gitignored).
5. **Do not install per-repo `post-checkout` hooks** during this
pass. The next sub-step covers future and existing repos
uniformly via the global hook.
#### Step P.3-whole-user — Install the global post-checkout hook (whole-user scope)
Skip if the operator picked per-project scope. **Dispatcher
flavour (Step P.0b):** skip step 1 below — Step P.3b-whole-user
installs the dispatcher as `post-checkout` instead, superseding the
standalone `git-global-post-checkout.sh`. Still run step 2 (set
`core.hooksPath`) here.
1. **(Simple flavour only) Install the universal `post-checkout`
hook.** Copy
`tools/agent-isolation/git-global-post-checkout.sh` into
`~/.claude/git-hooks/post-checkout` (or symlink it from
`~/.claude-config/git-hooks/post-checkout` if the operator
uses the private sync repo), mode `0755`. The hook content is
in
[`tools/agent-isolation/git-global-post-checkout.sh`](../../tools/agent-isolation/git-global-post-checkout.sh) —
it calls the sandbox-allowlist helper for Claude-Code-aware
worktrees.
2. **Set `core.hooksPath` globally** so every git operation across
every repo on the host uses the shared hook dir:
```bash
git config --global core.hooksPath "$HOME/.claude/git-hooks"
```
Surface the resulting `git config --global --get core.hooksPath`
value to the operator to confirm the write.
3. **Reiterate the implication** from Step P.0a's disclosure:
per-repo `.git/hooks/*` are now inert across the entire host.
The operator must migrate any per-repo hooks they want to keep.
`git config --global --unset core.hooksPath` is the reversal.
After this step, future `git clone`, `git worktree add`, and
`git checkout` operations anywhere on the host invoke the
framework's universal post-checkout, which keeps each
Claude-Code-aware project's `.claude/settings.local.json` in
sync without any further operator action.
#### Step P.3b-whole-user — Install the per-repo dispatcher + prek shim (dispatcher flavour only)
Skip unless the operator picked the **dispatcher** flavour in
Step P.0b. This is what keeps the operator's per-repo hooks (prek,
pre-commit, husky, hand-written) firing under global
`core.hooksPath` — the shared dir runs the framework's logic **and
then chains through to each repo's own `.git/hooks/<name>`**. See
[`docs/setup/secure-agent-setup.md` → *Whole-user with the per-repo dispatcher*](../../docs/setup/secure-agent-setup.md#whole-user-with-the-per-repo-dispatcher)
for the full rationale + the validated behaviour matrix.
1. **Install the dispatcher for each hook type.** Copy
[`tools/agent-isolation/git-hook-dispatcher.sh`](../../tools/agent-isolation/git-hook-dispatcher.sh)
into `~/.claude/git-hooks/git-hook-dispatcher.sh` (or symlink
from `~/.claude-config/git-hooks/` under the private sync repo),
mode `0755`, then create one symlink per hook name pointing at
it — including `post-checkout`, which replaces the standalone
file from Step P.3 (the dispatcher runs the same sandbox sync,
then chains to any repo-local `post-checkout`):
```bash
cd ~/.claude/git-hooks
for h in post-checkout pre-commit prepare-commit-msg commit-msg \
post-commit pre-push post-merge post-rewrite \
pre-rebase pre-merge-commit; do
ln -sf git-hook-dispatcher.sh "$h"
done
```
The dispatcher is basename-keyed, so one file serves every hook
type. It resolves the repo-local hook via
`git rev-parse --git-common-dir` (worktree-safe) and `exec`s it
with the original argv + inherited stdin, so a failing local
`pre-commit` / `pre-push` still aborts the git operation.
2. **Install the `prek` PATH shim** so `prek install` writes its
shim into the repo-local `.git/hooks/` (where the dispatcher
chains) instead of the shared dir. Copy
[`tools/agent-isolation/prek-shim.sh`](../../tools/agent-isolation/prek-shim.sh)
into `~/.claude/bin/prek` (or symlink from
`~/.claude-config/bin/prek`), mode `0755`. The shim rewrites
only `prek install` (injecting
`--git-dir "$(git rev-parse --git-common-dir)"` unless the caller
already passed `--git-dir`, asked for `--help`, or is outside a
git work tree); **every other `prek` invocation passes through
unchanged**. It is a no-op on hosts with no global
`core.hooksPath`.
3. **Surface the PATH line for the operator to add** to their
`~/.bashrc` / `~/.zshrc` (per the golden rules — never edit the
rc file directly):
```bash
export PATH="$HOME/.claude/bin:$PATH"
```
Confirm the rc path with the operator; print the line for them
to paste. Until it is on PATH ahead of the real `prek`, the
shim is inert and `prek install` reverts to writing into the
shared dir.
4. **Tell the operator how their existing prek repos are picked up.**
Repos that already ran `prek install` before this setup have
their shim in `.git/hooks/pre-commit` already (that is where a
pre-`core.hooksPath` `prek install` put it), so the dispatcher
finds them with no further action. Repos where they run
`prek install` *after* the global switch will now install
locally via the shim. Only repos where the shim already landed
in the shared dir (from a `prek install` run *while*
`core.hooksPath` was set but *before* this dispatcher setup)
need a one-time `prek install` re-run through the shim.
The `.` entry stays in the committed project-scope `allowRead`
regardless — the explicit absolute path in
`settings.local.json` is belt-and-braces, not a replacement. If
the harness ever stops resolving `.`, the explicit path still
covers the project; if `.` works correctly, the explicit entry is
redundant but harmless. The committed project-scope file is
**never** modified by the helper (machine-specific absolute paths
have no business in a file shared across contributors).
The helper is also invoked by `/magpie-setup adopt`,
`/magpie-setup upgrade`, and `/magpie-setup worktree-init` for
the same reason. The `post-checkout` git hook installed by
`/magpie-setup adopt` chains into the helper too, so new
worktrees added via `git worktree add` after this install pass
inherit access automatically — no operator action needed.
## After the install lands
Suggest two follow-up routines the user can wire later:
- `setup-isolated-setup-verify` — re-run after every Claude Code upgrade
or settings-file edit, to confirm denials still fire as
expected. The "did a denial silently turn into an allow?"
signal is exactly what this skill exists for.
- `setup-isolated-setup-update` — periodic check for framework
updates, pinned-tool upgrade candidates, and drift between the
installed user-scope copies and the framework's
source-of-truth. Recommend a per-Claude-Code-upgrade or
monthly cadence, whichever comes first.
**Always propose shared-config sync once the install lands.**
Regardless of whether the operator already maintains the
`~/.claude-config` sync repo, proactively offer to run
`setup-shared-config-sync` as a follow-up:
- If the `~/.claude-config` sync repo is already in place, the
skill commits + pushes the local modifications (the user-scope
scripts, hooks, and settings this install just wired up) so the
other machines pick them up.
- If the operator does **not** yet have `~/.claude-config`, the
`setup-shared-config-sync` skill bootstraps it (clones the
default private remote if it exists, or creates a fresh private
remote and scaffolds the layout). Mention this so a first-time
operator knows the follow-up will set sync up from scratch — the
point of proposing it is precisely so the just-installed config
does not stay machine-local.
Surface it as an offer for the operator to accept, not an
auto-run — the sync skill has its own confirmation gates before it
commits or pushes anything.