Skip to content

fix(install): repair the skills checkout guard, add --skip-skills - #1201

Merged
backnotprop merged 4 commits into
mainfrom
fix/install-checkout-guard
Aug 5, 2026
Merged

fix(install): repair the skills checkout guard, add --skip-skills#1201
backnotprop merged 4 commits into
mainfrom
fix/install-checkout-guard

Conversation

@backnotprop

@backnotprop backnotprop commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Two commits: the guard fix, and the opt-out CI needs once the guard actually fires.

1. Guard fix

A failed skills checkout reported success: no skills landed, yet the installer printed "YOU'RE ALL SET!" and exited 0, blaming "Tag vX.Y.Z predates the per-agent skill layout" rather than the failed clone.

It ran as ( set -e; ... ) || checkout_failed=1. POSIX ignores set -e for every command of an AND-OR list except the last, and bash 3.2.57 (what curl | bash gets on macOS), bash 5.3, dash, zsh and ksh all carry that into the subshell. The set -e was inert, so the subshell exited on its trailing if, which is 0. Fix: drop it, add explicit || exit 1 to the four fetch steps. Everything after the checkout stays best-effort, matching install.cmd.

2. Why CI needed --skip-skills

install-script-smoke installs a synthetic v9.9.9. Its fake curl serves the built binary for any URL, but skills come from git clone --branch v9.9.9 against real github.com, where that tag cannot exist. That clone has always failed, unnoticed only because the installer exited 0. With the guard repaired the step correctly dies.

So --skip-skills is a real opt-out, following the --skip-codex / -gemini / -kiro / -opencode family across all three installers: flag (-SkipSkills in PowerShell), PLANNOTATOR_SKIP_SKILLS_INSTALL, skipInstall.skills, flag > env > config. It covers every scope the checkout writes, plus the extras and cleanup sweeps. Skip means do-not-write: nothing installed is replaced or removed, git stops being required, and the banner stops claiming the /plannotator-* commands are ready.

Testing

env -i sandbox, bash 3.2.57, fake curl, local stand-in remote.

  • All three layers skip and name their source; flag beats env=0, env=0 beats config true, "skills": false stays a veto.
  • No regression: runs without the flag, before and after, give byte-identical trees (39 entries) and identical logs.
  • Guard intact: a bad clone URL without the flag still exits 1 with the fetch error.
  • Never removes: a skipped re-run leaves all 12 installed skill and command files byte-identical.
  • CI step reproduced locally: both run_installer calls exit 0, Codex assertions pass.

bash -n clean on 3.2.57 and 5.3.15. No PowerShell available here, so install.ps1 and install.cmd were reviewed structurally, not executed.

The skills/commands checkout runs in a subshell written as
`( set -e; ... ) || checkout_failed=1`. POSIX ignores `set -e` for every
command of an AND-OR list except the last, and bash 3.2.57 (what
`curl | bash` gets on macOS), bash 5.3, dash, zsh and ksh all carry that
suppression into the subshell. The `set -e` was inert.

A failed clone therefore ran the whole block anyway, the subshell exited
with the status of its trailing `if` (0), `checkout_failed` stayed 0, and
the installer printed "YOU'RE ALL SET!" with no skills installed. It also
blamed the wrong thing, printing "Tag vX.Y.Z predates the per-agent skill
layout" when the real cause was a failed clone.

Drop the inert `set -e` and guard the four fetch steps with explicit
`|| exit 1`. Everything after the checkout stays best-effort, matching
install.cmd, which only checks git clone and lets every xcopy run
unchecked. A local cp/mkdir/rm failure must not surface as the
"network or git error" message.

Verified on bash 3.2.57 in a sandboxed HOME: the failure case now exits 1
with the fetch error and no success banner, and original vs patched
success runs produce byte-identical trees (74 paths, 35 files) and
identical logs.
The skills and slash commands come from a sparse `git clone` of the release
tag. There was no way to decline that fetch short of --minimal, which also
drops the sem sidecar, the agent-terminal runtime, the hooks, and every
per-agent config. Anything that installs a tag github.com cannot serve had
no option at all.

Add --skip-skills to all three installers, following the existing
--skip-codex / --skip-gemini / --skip-kiro / --skip-opencode family: CLI
flag (-SkipSkills in PowerShell), PLANNOTATOR_SKIP_SKILLS_INSTALL env var,
skipInstall.skills config key, resolved flag > env > config. It is not a
per-agent switch; it covers every scope the checkout writes (Claude,
~/.agents, OpenCode, Gemini, Kiro), the extras, and the skill-scope cleanup
sweeps. Skip means do-not-write: nothing already installed is replaced or
removed, and git stops being a hard requirement. The run reports
"Skills: skipped (<source>)" and the closing banner no longer claims the
/plannotator-* commands are ready, which is the same false-success the
checkout guard exists to prevent.

Use it in the install-script-smoke job. That job installs a synthetic
v9.9.9: the fake curl serves the freshly built binary for any URL, but the
skills clone goes to real github.com, where the tag does not and cannot
exist. That clone has always failed; it only went unnoticed while the
broken guard let the installer exit 0 anyway. The job asserts Codex hook
config, not skills, so it opts out rather than ignoring a real error. Both
run_installer call sites go through the one function definition.

Verified in an env -i sandbox on bash 3.2.57 (what `curl | bash` gets on
macOS) with a fake curl and a local stand-in remote. Flag, env var, and
config each skip and name their own source; flag beats env=0; env=0 beats
config true; an explicit "skills": false stays a veto. Without the flag,
pre-change and post-change runs produce byte-identical trees (39 entries)
and identical logs. A bad clone URL without the flag still exits 1 with the
fetch error and no success banner. A --skip-skills re-run over an existing
install leaves all 12 skill and command files byte-identical. The CI step
was reproduced locally: both run_installer calls exit 0 and every Codex
assertion still passes.
@backnotprop backnotprop changed the title fix(install): make a failed skills checkout stop reporting success fix(install): repair the skills checkout guard, add --skip-skills Aug 5, 2026
scripts/install.test.ts asserts against exact install-script source text, so
six assertions broke when --skip-skills landed. Each is repointed at the new
string with its intent preserved, not weakened:

- The "hook/config writing happens before the git hard-fail" ordering test
  keeps proving the ordering; it just matches the gate's new conditional
  form. git being a hard requirement is now a narrower invariant (it applies
  only when the checkout actually runs), so that is asserted separately
  rather than dropped.
- The skipInstall walk assertions follow codex/gemini/kiro/opencode gaining
  a skills entry, in install.sh's `for _agent` loop and install.cmd's
  PowerShell key list.
- The three "never remove" sweep assertions follow the Codex stale-skill
  cleanup gaining its skills-opt-out arm, in all three installers.

Add nine tests covering --skip-skills itself in the same style as the
per-agent family: flag/switch parsing, PLANNOTATOR_SKIP_SKILLS_INSTALL,
skipInstall.skills, and flag > env > config precedence by textual layering,
for each installer. Each installer also gets a test that the opt-out bails
before the clone and leaves the checkout guard intact (#1201's fix must keep
failing a real fetch error), and one that the run reports honestly, never
prints the "commands are ready" banner over an empty skills dir, and
suspends the extras, the model-invocation rewrite, and the stale-stub sweeps
rather than applying them partially.

bun test scripts/: 116 pass, 6 skip, 0 fail. Full bun test: 2884 pass,
234 skip, 0 fail. bun run typecheck clean.
@backnotprop
backnotprop merged commit 10a5104 into main Aug 5, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant