Skip to content

Publish the fizzy skill to basecamp/skills on release - #214

Merged
jeremy merged 9 commits into
masterfrom
sync-skills
Sep 14, 2026
Merged

jeremy merged 9 commits into
masterfrom
sync-skills

Conversation

@jeremy

@jeremy jeremy commented Sep 13, 2026

Copy link
Copy Markdown
Member

Why

basecamp/skills is being repositioned as the skills repo for all 37signals products (basecamp/skills#6), installable with one npx skills add basecamp/skills. basecamp-cli and hey-cli already publish their skills there on every stable release; Fizzy is the one CLI that does not, so skills/fizzy only ever reaches agents through the binary or this repo's tree.

What

  • scripts/sync-skills.sh — byte-identical to the seed's seed/scripts/sync-skills.sh in Seed the skills sync with per-source manifests and the skills/ layout cli#78 at 1df3bfe5 apart from the CLI_NAME default (fizzy, so SYNC_SOURCE is fizzy-cli: bot fizzy-cli[bot], commit Sync skills from fizzy-cli <tag>, provenance basecamp/fizzy-cli@<sha> from GITHUB_REPOSITORY) and the seed's # TODO line. basecamp-cli#708 and hey-cli#434 are converging on the same file, so every publisher runs one script.
  • Per-source manifest. The script records what it published in .managed-skills.fizzy-cli at the target root and only ever removes a skills/<name> that its own manifest lists, the release no longer ships, and no other CLI's manifest claims. A target with no .managed-skills.fizzy-cli yet — which is what the first Fizzy release will find — removes nothing. The manifest is per-source because the shared one let each CLI's release delete the others' skills (The hey-cli bot deleted the Basecamp skills skills#5: hey-cli's sync deleted skills/basecamp and skills/basecamp-doctor, basecamp-cli's deleted skills/hey twice). The legacy .managed-skills is rewritten as a comment-only tombstone so a sibling still on the pre-fix script deletes nothing either.
  • Beyond the first cut (the seed's additions): a name another source's manifest holds is refused before anything is copied; a rejected push (fetch first or non-fast-forward) resets to the remote's new tip, re-applies the sync there with the ownership checks, and pushes again; SYNC_SOURCE and DRY_RUN are validated; and the token reaches git as a github.com-scoped http.extraheader in a private mode-600 GIT_CONFIG_GLOBAL instead of the remote URL or an insteadOf rewrite (which git expands into git-remote-https's argv), so it never appears in argv or the clone's config. The script always clones the target fresh and pushes only the commit it made, so there is no checkout to hand it.
  • sync-skills job in release.yml — mirrors hey-cli's: stable tags only, needs: [release], continue-on-error: true so a stale skills repo never fails a release that has already shipped, concurrency group sync-skills, release environment, and the check the manual workflow makes — the tag must be the repository's latest stable release, or the sync is skipped — so an older release whose run stalls, or whose failed sync is rerun after a newer one has shipped, cannot roll basecamp/skills back (the concurrency group serialises the syncs but does not order them), token minted per-run and scoped to basecamp/skills with contents write. On failure it files or comments on a skills-sync-labeled issue here (label-keyed and fail-closed so a retitled issue or an API error cannot spawn duplicates; the label is created idempotently on first use since this repo does not have it yet).
  • sync-skills.yml — hey-cli's manual recovery workflow: workflow_dispatch with a stable tag and an optional dry run, refusing anything but the latest stable release so it cannot roll the distribution repo back, running the sync logic from the dispatching ref against the tag's skills tree.
  • scripts/test-sync-skills.sh — the seed's test, unchanged. Plain bash (BATS is not in this repo's toolchain: make tools and CI install none). It runs the script as two CLIs against a local bare basecamp/skills repository seeded with the state The hey-cli bot deleted the Basecamp skills skills#5 left — real clones, commits and pushes, no network: interleaved A, B, A, B asserting both sources' skills, both manifests and the tombstone after each step; the copy filter dropping *.go, dotfiles and dot-directories; a dropped skill removing only its own directory; a pre-fix sibling's rewrite of the legacy manifest deleting nothing; a name in two manifests surviving with a warning; publishing a sibling-owned name refused with nothing changed; DRY_RUN=remote showing the diff without committing; the offline DRY_RUN=local preview; a real push against a local bare origin that a sibling moved first (retry lands on top of the sibling's commit) and one where the sibling claims the name mid-race (retry refuses, origin untouched); the sibling dropping that claim (the next release publishes); and a github.com target without a token refused before anything is cloned. Wired as make test-sync-skills (now part of make check) and as a "Test the skills sync" step in test.yml's test job, which release.yml also calls — the same wiring as the seed's Makefile and test.yml.
  • scripts/sync-skills.sh added to the sensitive-change gate's extra patterns alongside publish-aur.sh, since it pushes to another repo with a minted token.
  • RELEASING.md — step 10 in the release list, the prerelease-behavior and distribution-channel tables, the CI Secrets note (the tap and skills tokens are both minted from cli-release-bot, each scoped to its one repo), and a new "Skills sync" section covering ownership, the offline preview, the failure issue and the recovery workflow. AGENTS.md's tree comment notes the mirror.

What gets published

DRY_RUN=local from this branch's root:

Found 1 skill(s) in skills/: fizzy

=== Skills copied ===
  skills/fizzy/SKILL.md

=== Diff (against empty baseline) ===
 skills/fizzy/SKILL.md | 1159 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1159 insertions(+)

skills/ holds only fizzy/SKILL.md and embed.go; the copy filter drops *.go and dotfiles, so SKILL.md is the whole of it.

For the maintainer to confirm: the app on basecamp/skills

The job mints its token from vars.RELEASE_CLIENT_ID / secrets.RELEASE_APP_PRIVATE_KEY in this repo's release environment, exactly as the existing tap push does, but asks for repositories: skills. That only works if the app behind those credentials is installed on basecamp/skills. What I could verify: RELEASE_CLIENT_ID is the same value (Iv23liAbERu6z8d7t5A8, cli-release-bot) in the release environments of fizzy-cli, basecamp-cli and hey-cli, and basecamp/skills' history carries pushes from both basecamp-cli[bot] and hey-cli[bot], so that app is installed there with contents write. This repo's RELEASE_APP_PRIVATE_KEY already mints a working token for homebrew-tap on every release, so the key is that app's. What I cannot exercise from here is the mint for repositories: skills from this repo's environment, so please confirm it on the first stable release. The job is continue-on-error, so if the mint fails the release still ships; the skills-sync issue and the Sync skills workflow are the way back.

Testing

  • diff scripts/sync-skills.sh <seed>/seed/scripts/sync-skills.sh at 1df3bfe5: only the # TODO line and the CLI_NAME default differ
  • actionlint, zizmor and shellcheck over the edited workflow and script: clean
  • scripts/test-sync-skills.sh: all assertions passed (locally and in CI's test job)
  • shellcheck scripts/sync-skills.sh scripts/test-sync-skills.sh: clean
  • actionlint and zizmor . (what make lint-actions and the lint-actions job run): no findings
  • make check passes (fmt, vet, lint, tidy, race-test, test-sync-skills)

basecamp/skills is becoming the skills repo for every 37signals product,
and Fizzy was the one CLI not publishing there. Stable releases now mirror
skills/ into it with the same sync-skills.sh basecamp-cli and hey-cli run,
which owns only this CLI's skills via a per-source manifest
(.managed-skills.fizzy-cli) and never touches a sibling's
(basecamp/skills#5). The job is continue-on-error and files a
skills-sync issue on failure; sync-skills.yml is the manual recovery.

The sibling repos' BATS test is ported to plain bash, since this repo's
toolchain has no BATS, and runs as make test-scripts (part of make check)
and in CI.
Copilot AI balanced review requested due to automatic review settings September 13, 2026 00:28
@github-actions

Copy link
Copy Markdown

Sensitive Change Detection (shadow mode)

This PR modifies control-plane files:

  • .github/workflows/release.yml
  • .github/workflows/sensitive-change-gate.yml
  • .github/workflows/sync-skills.yml
  • .github/workflows/test.yml

Shadow mode — this check is informational only. When activated, changes to these paths will require approval from a maintainer.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-14T05:13:25.048287Z 14d039f New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Invalid DRY_RUN values can unexpectedly execute the production push path instead of failing safely.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Publishes Fizzy’s agent skill to basecamp/skills during stable releases, with safe ownership tracking and manual recovery.

Changes:

  • Adds skill synchronization and release workflows.
  • Adds shell-based ownership and safety tests to CI.
  • Documents the publishing and recovery process.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File summaries
File Description
scripts/sync-skills.sh Implements synchronization and ownership manifests.
e2e/sync_skills_test.sh Tests synchronization behavior.
.github/workflows/release.yml Publishes skills after stable releases.
.github/workflows/sync-skills.yml Adds manual recovery workflow.
.github/workflows/test.yml Runs shell tests in CI.
.github/workflows/sensitive-change-gate.yml Protects the publishing script.
Makefile Adds script-test targets.
RELEASING.md Documents publishing and recovery.
AGENTS.md Notes the external skills mirror.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 2
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/sync-skills.sh
Comment thread e2e/sync_skills_test.sh Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ed19d80c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml
Comment thread scripts/sync-skills.sh Outdated
Comment thread scripts/sync-skills.sh Outdated
The seed (basecamp/cli#78) went further than the hey-cli copy taken
first: a rejected push re-applies the sync from the remote's new tip
(matching git's "fetch first" as well as "non-fast-forward"), a name
another source's manifest holds is refused before anything is copied,
SYNC_SOURCE and DRY_RUN are validated, a dirty checkout is refused, and
the token reaches git through a private config rather than the remote
URL. basecamp-cli and hey-cli are converging on the same file, so every
publisher runs one script; only the CLI_NAME default differs here.

The seed's test-sync-skills.sh replaces the bash port of the BATS file:
it covers the same ownership cases plus the racing publisher, the
publish-side refusal, DRY_RUN=remote and the dirty-tree guard.
…ards

Mirrors basecamp/cli@966966e (the seed is the source of truth; only the
CLI_NAME default differs here), and picks up the two seed commits this copy
was behind: the remote-URL assert that read every configured URL, and two
comment rewordings — both now moot, since the assert is gone.

SKILLS_TARGET let the script adopt an existing checkout of basecamp/skills.
Every release path clones its own target, and each review round found
another corner of "any checkout" to guard. The script now always clones
into a temp directory from SKILLS_REPO_URL (default
https://github.com/basecamp/skills.git, the token carried through the same
insteadOf rewrite as before), applies, pushes and cleans up, so the only
commit it can push is the one it made. The remote-URL, branch and
clean-tree asserts are gone with the knob; the retry from the fetched tip,
the per-source manifests, the collision guard, the tombstone and DRY_RUN
validation are unchanged.

The test points SKILLS_REPO_URL at a local bare repository and reads every
result back from a clone of its own; the race is staged with a post-commit
hook that pushes the sibling's commit between the script's clone and push.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ed326de328

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml
Mirrors the seed (basecamp/cli#78 at 1df3bfe5). GIT_CONFIG_GLOBAL stands in for
the user's global file only. The system config and any GIT_CONFIG_COUNT settings
in the environment still reach every git call the script makes — the race test
injects its hooks path through exactly that — so the comment no longer claims the
ambient environment is kept out.
…release

The sync-skills job's concurrency group serialises the syncs but does not order
them by version. An older stable release's job can reach the sync after a newer
one has published — its run stalled, or its failed sync was rerun later, which is
what the failure issue tells the operator to do — and the script mirrors the
skills wholesale, so that run would roll basecamp/skills back to the older skill.

The job now makes the check the manual Sync skills workflow already makes: the
tag must be the repository's latest stable release, or the sync is skipped with
a warning. A failed lookup fails the job, so the failure issue is filed rather
than the sync skipped in silence.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The repository-specific fizzy-cli identity default is not exercised by the integration suite.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.

Comment thread scripts/test-sync-skills.sh Outdated
@jeremy

jeremy commented Sep 14, 2026

Copy link
Copy Markdown
Member Author

Review threads: 1 resolved (1 fixed, 0 declined).

Every case set SYNC_SOURCE to play hey-cli or basecamp-cli, so the one line
this copy of the script changes — the CLI_NAME default — never ran. A last case
runs the script with neither SYNC_SOURCE nor CLI_NAME set and checks the
manifest it writes, the bot it commits as and the summary line against the
source the Makefile expects, fizzy-cli. A copy left at the seed's placeholder,
or naming another CLI, fails here instead of publishing under that source's
manifest and identity.

Mirrors seed/scripts/test-sync-skills.sh from basecamp/cli@19b52ed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approved

The sync behavior is defensively implemented, comprehensively tested, and consistently integrated into release and recovery workflows.

Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a917e941c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Makefile
… rewrite

Mirrors the seed (basecamp/cli#78 at 0649a4d); scripts/sync-skills.sh stays
byte-identical apart from the CLI_NAME default, scripts/test-sync-skills.sh
byte-identical.

The insteadOf rewrite kept the token out of the outer git argv and the remote
URL, but git expands the rewrite before invoking git-remote-https, so that helper
still received https://x-access-token:<token>@github.com/... as an argument. The
private gitconfig now carries http.https://github.com/.extraheader with the
basic-auth form actions/checkout writes: the helper sees the plain URL, and the
token lives only in the mode-600 file until the tmpdir goes.

The test unsets SKILLS_TOKEN up front: its token-required case points the script
at the real basecamp/skills, and a token inherited from the caller's environment
would have let it clone and publish the fixtures there.

The header now says exactly what the tombstone shields — the sources that have
upgraded — rather than "anyone's skills": a pre-fix sibling still rewrites
.managed-skills with its own names and a second pre-fix sibling still deletes
those, the basecamp/skills#5 clobber confined to the CLIs yet to upgrade.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 14d039fbee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/sync-skills.yml
@jeremy

jeremy commented Sep 14, 2026

Copy link
Copy Markdown
Member Author

Review threads: 2 resolved (2 fixed, 0 declined).

14d039f mirrors the seed (basecamp/cli#78 at 0649a4d): scripts/sync-skills.sh byte-identical apart from the CLI_NAME default, scripts/test-sync-skills.sh byte-identical — the github.com-scoped http.extraheader in place of the insteadOf rewrite, the test's unset SKILLS_TOKEN, and the qualified tombstone header, the same bytes basecamp-cli#708 and hey-cli#434 carry. AGENTS.md's make check list now includes test-sync-skills.

@jeremy

jeremy commented Sep 14, 2026

Copy link
Copy Markdown
Member Author

Review threads: 1 more resolved (0 fixed, 1 declined with the reasoning in the thread) — 9 of 9 resolved.

Declined:

  • Publish the fizzy skill to basecamp/skills on release #214 (comment) — an in-workflow ref check cannot guard the ref the workflow file itself came from; the release environment (required reviewers, deployment branch policy) is the binding control, and a v* tag push runs the same script with the same token through the same gate. Same shape hey-cli and basecamp-cli already run.

Bots on 14d039f: Codex reviewed it (the thread above). Copilot last approved a917e94 and has not reported on 14d039f after 35 minutes, though it reviewed each earlier push within minutes; a re-review cannot be requested by API, so a human can request it from the Reviewers panel if wanted. The diff from a917e94 is the seed mirror plus one AGENTS.md line — the same script bytes Copilot approved on basecamp-cli#708 at 4ec7f53.

@jeremy
jeremy merged commit 4e4607d into master Sep 14, 2026
16 checks passed
@jeremy
jeremy deleted the sync-skills branch September 14, 2026 05:42
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.

2 participants