Skip to content

fix(ci): unblock release + single-source skills via Vite plugin#93

Merged
JamesLawton merged 6 commits intomainfrom
fix/lerna-release-jq-arg-max
Apr 21, 2026
Merged

fix(ci): unblock release + single-source skills via Vite plugin#93
JamesLawton merged 6 commits intomainfrom
fix/lerna-release-jq-arg-max

Conversation

@MaximusHaximus
Copy link
Copy Markdown
Contributor

@MaximusHaximus MaximusHaximus commented Apr 21, 2026

Failing run: https://github.com/0xPolygon/polygon-agent-cli/actions/runs/24717552967/job/72297775568

The problem

Release failed at `lerna-signed-release.sh` Stage 3 with `jq: Argument list too long`. Pulling the thread surfaced three issues worth fixing together:

  1. That release had enough files in its diff to hit ARG_MAX because a pre-commit hook was maintaining four duplicated copies of `skills/` across the repo. GitHub web-UI edits bypassed the hook and left copies stale until someone's next local commit inherited the drift; editing the wrong copy silently had the change overwritten.
  2. Nothing reads the CLI's bundled `skills/`. CLI source never touches it, Claude Code doesn't auto-discover from npm packages, and the documented install tool (`npx skills add …`) reads from GitHub, not npm. It's been dead weight in every tarball since 0.1.2.
  3. Skills reference explicit CLI flags that drift per release. An agent on an older CLI fetching the latest CDN skill hits `yargs: Unknown argument` with no pointer at the version mismatch.

Approach

  • Fix jq with `--slurpfile` — spill to temp files instead of a giant `--argjson`.
  • Single source of truth at `skills/`, served to the CDN by `vite.config.ts:mirrorSkills()` (middleware for dev, `generateBundle` for build). Dynamic — new sub-skills auto-publish.
  • Drop `skills` from the CLI's `files:`. Published tarball no longer ships it.
  • Add a self-diagnostic blurb to the root SKILL.md pointing at `polygon-agent --version` and the upgrade command.

Why not marked breaking

Pre-1.0 semver: 0.8.0 → 0.9.0 via `feat(cli):` (no `!`, no `BREAKING CHANGE:`, which would bump major). No consumer of the bundled path has been found anywhere. If a silent downstream existed, 0.9.1 with a pointer to `npx skills add` closes it.

Reviewer gotchas

  • CDN URL source changed. `/polygon-defi/SKILL.md`, `/polygon-discovery/SKILL.md`, `/polygon-polymarket/SKILL.md` used to come from committed `public/` copies; they now come from the plugin's `generateBundle` output. Every older-CLI's bundled root SKILL.md references these URLs, so please verify post-deploy that they still resolve.
  • `vite.config.ts` now owns route logic. Non-obvious that a build config governs the public URL surface.
  • New sub-skills auto-publish at both `/skills//` and `//` on next build. No gate — add one in the plugin if desired.

Test plan

  • Merge and trigger a release from `main` — Stage 3 builds the GraphQL body without ARG_MAX.
  • `@polygonlabs/agent-cli@0.9.0` tarball ships with no `skills/`.
  • After connector-ui deploy: `/SKILL.md`, `/skills/…`, and each `/polygon-*/SKILL.md` still resolve.
  • Adding a future `skills//SKILL.md` serves at both `/skills//` and `//` with no further config change.

The release workflow was failing at the GraphQL-payload stage with
`/usr/bin/jq: Argument list too long` whenever a release touched more
than a handful of sizeable files. The base64-encoded contents of every
changed file were being passed to jq as a single `--argjson` value,
which blows past Linux's ~128 KB per-argument ARG_MAX ceiling once a
few CHANGELOG / SKILL.md updates accumulate.

Spill the additions and deletions JSON to temp files and load them via
`--slurpfile`, unwrapping with `$additions[0]` / `$deletions[0]`. The
GraphQL body shape is unchanged; only the jq transport is different.
Temp files are added to the existing EXIT trap.
@MaximusHaximus MaximusHaximus force-pushed the fix/lerna-release-jq-arg-max branch from 5417c91 to 73a7d9d Compare April 21, 2026 13:46
@MaximusHaximus MaximusHaximus changed the title fix(ci): spill release additions/deletions through jq --slurpfile fix(ci): unblock release + symlink duplicated SKILL.md paths Apr 21, 2026
@MaximusHaximus MaximusHaximus force-pushed the fix/lerna-release-jq-arg-max branch from 73a7d9d to 692908e Compare April 21, 2026 14:49
@MaximusHaximus MaximusHaximus changed the title fix(ci): unblock release + symlink duplicated SKILL.md paths fix(ci): unblock release + stop duplicating + bundling SKILL.md Apr 21, 2026
@MaximusHaximus MaximusHaximus force-pushed the fix/lerna-release-jq-arg-max branch from 692908e to 05997e1 Compare April 21, 2026 14:58
Comment thread packages/connector-ui/vite.config.ts Fixed
@MaximusHaximus MaximusHaximus changed the title fix(ci): unblock release + stop duplicating + bundling SKILL.md fix(ci): unblock release + single-source skills via Vite plugin Apr 21, 2026
@polygonlabs/agent-cli no longer ships a skills/ directory inside
the published tarball. Installing the CLI via npm has never been
the agent-facing skill install path — the recommended flow is
`npx skills add https://github.com/0xPolygon/polygon-agent-cli`,
which reads from the GitHub repo, not from npm. The bundled copy
was vestigial: no CLI code reads it, no postinstall hook registers
it, Claude Code does not auto-discover skills from installed npm
packages, and the `skills` CLI (vercel-labs/skills) has no
npm-package resolver at all — its `parseSource` function accepts
GitHub/GitLab/local paths only.

Removing it also deletes the duplicated copies under
packages/connector-ui/public/ (root SKILL.md, skills/ tree, and
per-sub-skill root mirrors) and packages/polygon-agent-cli/skills/
that the pre-commit hook was keeping in sync. The sync hook itself
goes too — nothing left to sync. skills/ at the repo root is now
the single source of truth.

Note: this is technically a published-surface change — the tarball
has shipped the skills/ path since 0.1.2. Pre-1.0 semver accepts
surface changes in minor bumps, so this is intentionally NOT marked
with a `!` or BREAKING CHANGE footer; @polygonlabs/agent-cli will
bump 0.8.0 -> 0.9.0. No consumer has been found in or outside the
repo that reads the bundled path.

A following commit adds a Vite plugin that serves the CDN URL
surface (/SKILL.md, /skills/*, /<sub-skill>/SKILL.md) directly from
skills/ at build time, so the agentconnect URLs keep resolving with
no committed duplicates.
A small Vite plugin (mirrorSkills) mirrors the canonical skills/
directory at the repo root onto the CDN URL surface in memory — no
symlinks, no committed duplicates. Three URL patterns, all derived
dynamically from skills/ contents:

  /SKILL.md               -> skills/SKILL.md
  /skills/<...>           -> skills/<...>                (full tree)
  /<sub-skill>/SKILL.md   -> skills/<sub-skill>/SKILL.md
                            (compat for URLs referenced in older
                             CLIs' bundled root SKILL.md)

configureServer handles dev requests via middleware; generateBundle
emits every path into dist/ for the Cloudflare Pages build. Adding
skills/<new-skill>/SKILL.md starts serving both /skills/<new-skill>/
and /<new-skill>/ automatically on the next dev-server start or
build — this restores the dynamism the old pre-commit hook had,
without committing anything to git.

Verified that `pnpm run build` emits all 8 expected SKILL.md paths
(root, four under /skills/, three sub-skill siblings at root), each
with the correct per-skill content.
Skills reference explicit polygon-agent commands and flags — a CLI
predating a given skill revision will fail with "Unknown argument"
or "command not found" when the agent follows example invocations.
Without a self-diagnostic pointer, the agent has no way to identify
this as a version-drift issue.

Add a compact section right after Prerequisites pointing the agent
at `polygon-agent --version`, `npm view @polygonlabs/agent-cli
version`, and the upgrade command. Placed in the root SKILL.md only
— it is always read first, so one well-placed note covers every
sub-skill that inherits its commands from the same CLI.
@MaximusHaximus MaximusHaximus force-pushed the fix/lerna-release-jq-arg-max branch from 05997e1 to 7cde2a0 Compare April 21, 2026 15:03
vite.config.ts had grown to ~100 lines with the inline plugin; a build
config file that quietly owns a chunk of the public URL surface is
harder to discover than a plugin with a descriptive filename. Move
the plugin into packages/connector-ui/vite-plugins/mirror-skills.ts
and import it.

Behaviour-preserving move — build output is byte-identical. The
following commit tightens the emit contract.

Also extend tsconfig.node.json's include to cover the new plugin
file so it gets typechecked alongside vite.config.ts.
…aths

The recursive walk that emitted /skills/<...> was inherited from the
old pre-commit hook's copyDir. It would blindly publish every file
under skills/ — a stray .DS_Store, a repo-local README explaining
the skill-authoring convention, lint config, any future
skills/examples/foo.md would all reach the CDN with no gatekeeping.

Drop the walk. The SKILL protocol defines exactly one file per skill
directory (SKILL.md), so the emit surface is now an explicit
enumeration of that contract:

  /SKILL.md                     <- skills/SKILL.md
  /skills/SKILL.md              <- skills/SKILL.md
  /skills/<sub-skill>/SKILL.md  <- skills/<sub-skill>/SKILL.md
  /<sub-skill>/SKILL.md         <- skills/<sub-skill>/SKILL.md
                                   (flattened mirror, compat with
                                    URLs in older CLIs' root SKILL.md)

Anything else in skills/ — whether accidental (.DS_Store) or
intentional (examples, images) — is not served. If a future
sub-skill needs non-SKILL.md assets, extend the plugin explicitly.

Middleware's resolveRequest tightened to match: only recognises
/SKILL.md, /skills/SKILL.md, /<name>/SKILL.md, and
/skills/<name>/SKILL.md. Path traversal guarded by the regex
(single path component only) plus the existing withinSkillsDir
check for defence in depth.

Build output unchanged — same 8 SKILL.md paths in dist/ with
identical byte content to the previous commit.
const file = resolveRequest(url);
if (file && withinSkillsDir(file) && existsSync(file) && statSync(file).isFile()) {
res.setHeader('content-type', 'text/markdown; charset=utf-8');
res.end(readFileSync(file));
@MaximusHaximus MaximusHaximus marked this pull request as ready for review April 21, 2026 15:23
@JamesLawton JamesLawton merged commit b71c97f into main Apr 21, 2026
3 of 4 checks passed
@JamesLawton JamesLawton deleted the fix/lerna-release-jq-arg-max branch April 21, 2026 15:32
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.

3 participants