Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 40 additions & 16 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -252,22 +252,46 @@ jobs:
fi
done

clawhub skill publish \
--slug euthyna --name euthyna --version "$PKG" \
--categories security,development \
--topics "security-audit,code-audit,git-forensics,owasp" \
--source-repo "${{ github.repository }}" --source-commit "$SHA" \
--source-path .agents/skills/euthyna \
--changelog "$CHANGELOG" \
--tags latest .agents/skills/euthyna
clawhub skill publish \
--slug euthyna-en --name "euthyna (English)" --version "$PKG" \
--categories security,development \
--topics "security-audit,code-audit,git-forensics,owasp" \
--source-repo "${{ github.repository }}" --source-commit "$SHA" \
--source-path .agents/skills/euthyna-en \
--changelog "English edition. Changes in $PKG: $CHANGELOG" \
--tags latest .agents/skills/euthyna-en
# The registry holds a version row before the `latest` pointer moves, and
# it refuses a submission for a version it already carries — measured on
# 0.9.0, where the Chinese edition landed and the English edition was
# refused `Version 0.9.0 already exists` although `euthyna-en` had no such
# version at all. That refusal ended the step under `bash -e`, so the
# second slug was never submitted and the verify step — the only place
# that could see the missing half — never ran. A duplicate refusal is
# therefore recorded and skipped here: whether the listing actually
# arrived is the verify step's judgement, not this command's.
failed=0
for slug in euthyna euthyna-en; do
if [ "$slug" = "euthyna" ]; then
name="euthyna"
changelog="$CHANGELOG"
else
name="euthyna (English)"
changelog="English edition. Changes in $PKG: $CHANGELOG"
fi
src=".agents/skills/$slug"
status=0
out="$( { clawhub skill publish \
--slug "$slug" --name "$name" --version "$PKG" \
--categories security,development \
--topics "security-audit,code-audit,git-forensics,owasp" \
--source-repo "${{ github.repository }}" --source-commit "$SHA" \
--source-path "$src" \
--changelog "$changelog" \
--tags latest "$src"; } 2>&1 )" || status=$?
printf '%s\n' "$out"
if [ "$status" -ne 0 ]; then
case "$out" in
*"already exists"*)
echo "::notice::$slug@$PKG refused as a duplicate — leaving it to the verify step"
continue ;;
esac
echo "::error::$slug@$PKG publish failed (exit $status)"
failed=1
fi
done
[ "$failed" = 0 ] || exit 1

# A push is not a listing. `clawhub skill publish` returns once the
# submission is queued; the registry then generates a skill card and runs
Expand Down
58 changes: 58 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,29 @@ Not inferred. Do not re-research these.
`.agents/skills/euthyna` and `.agents/skills/euthyna-en`. Its ClawHub job did not
publish — see `### ClawHub registry` below, where the listing was later brought to 0.7.0
from this machine.
- 🧪 `euthyna@0.8.0` published 2026-10-17 by the same workflow: the tag points at `7f6aaa4`,
and run `39069623839` took `preflight`, `publish` **and `clawhub`** green — the first
release whose ClawHub listing was moved by CI rather than rescued from this machine. GitHub
release `euthyna 0.8.0`, `publishedAt 2026-10-17T21:08:08Z`, author `github-actions[bot]`;
the attestations endpoint returns 200 for `euthyna@0.8.0` (15,086 bytes). This bullet was
written 2026-10-21: the release itself went four days without a record here, which is the
reason the 0.9.0 record is written in the same pass as the tag rather than after it.
- 🧪 `euthyna@0.9.0` published 2026-10-20 by the same workflow: tag `v0.9.0` → `af4aa79`
(the merge of the bump PR #41), run `39477379456` took `preflight` and `publish` green and
cut the GitHub release `euthyna 0.9.0` (`publishedAt 2026-10-20T18:44:47Z`, author
`github-actions[bot]`). Checked from outside: `npm view euthyna dist-tags` → `latest: 0.9.0`;
shasum `3242f13aaead91f480dae7ad0d5adc695444ed61`, 833,248 unpacked bytes; the attestations
endpoint returns 200 (15,039 bytes). A temp-prefix install from the registry carries
`src/graph.js` and both skill editions, its English help renders `--base` as optional and
`--graph`, and `euthyna audit --base v0.8.0 --head v0.9.0 --graph` draws the provenance
graph from the published binary — exit 10, which is `FLAGGED` in the published contract, not
an error. **Its `clawhub` job failed** — see `### ClawHub registry` below.

One CI-reading fact from the same release: the bump PR's `node 24 on windows-latest` check
reported `fail` after 15m1s, and `gh run view --json jobs` called the same job `cancelled` with
an empty log — the job sat queued and was cancelled, it never ran. The same commit's push run on
`main` (run `39476543157`) is 8 jobs green including that cell. So read a red Windows check as
suspicious until the job log exists; a cancelled queue slot is not a test failure.

### ClawHub registry

Expand Down Expand Up @@ -244,6 +267,39 @@ One thing this did settle: `gh secret list` cannot tell a set secret from an emp
`CLAWHUB_TOKEN: ***`, and preflight records `present`/`absent`. That is the evidence the
secret is populated — which the earlier `absent`-in-the-job failure could not show either way.

🧪 **A duplicate refusal on the second slug means the second edition was never submitted. Measured 2026-10-21 on 0.9.0.**

`v0.9.0` took `preflight` and `publish` green — npm `latest: 0.9.0`, provenance 200 — and its
`clawhub` job exited 1. The step log gives the order: the pre-read passed both slugs
(`euthyna currently lists 0.8.0; candidate 0.9.0 compares: newer`, same for `euthyna-en`), the
Chinese `publish` was accepted and landed, and the English `publish` was refused with
`Version 0.9.0 already exists. Increment the version number and try again. (reset in 22s)` — for
a slug that held no such version, because reading `euthyna-en --version 0.9.0` immediately
afterwards answered `Version not found`. **The refusal and the readable state are two different
claims; do not reconcile them into one.**

What cost the listing was the ordering, not the answer. The step ran the two publishes as flat
statements under `bash -e`, so the first non-zero exit ended it — the English edition never
reached the registry, and the verify step, the only thing in the job that could see a missing
half, never ran. One red job therefore covered two different facts (one listing landed, one did
not) and reported neither.

Both settled. The Chinese slug, 8 rounds at three-minute spacing after the CI submission:
rounds 1–4 `pass|clean|0.8.0`, rounds 5–6 `fail|card.missing|clean|0.9.0`, rounds 7–8
`pass|clean|0.9.0`. The English slug, pushed from this machine with the workflow's own flag set
(`--source-commit af4aa79`, CLI v0.23.3): rounds 1–2 `0.8.0`, then rounds 3–8
`fail|card.missing|clean|0.9.0` — `latest` moved at round 3 while the decision was still failing
23 minutes later. By 2026-10-21T00:56Z both read `pass|clean|0.9.0`; **when between 19:56Z and
00:56Z it flipped is not on record** — a one-shot re-read ran in that window and its report lives
in its own conversation, so this is the same attribution limit the 0.7.0 entry records. The
`card.missing` intermediate state is the `a push is not a listing` fact measured a third time,
this time on two slugs of one release: the pointer moves before the decision does, so reading
either alone under- or over-reports.

The workflow now walks the two editions in one loop, treats a duplicate refusal as
`already registered` and continues to the next slug, and lets a genuine failure redden the step at
the end — the verify step stays the only thing that decides whether a listing arrived.

npm running ahead of ClawHub is the exact condition PR #23 added this job to detect, so the
lag is visible as a red job plus these notes rather than silent.

Expand Down Expand Up @@ -680,6 +736,8 @@ is faithful — that is a reading job, and a mechanical stand-in would only manu
| **A workflow that only runs on release day can be broken for days and show nothing but ~0-second red runs** | 🧪 Measured 2026-10-10: `publish.yml` did not parse from `b795569` (PR #23) until the 0.7.0 rehearsal. The cause was `${{ runner.temp }}` in a **job-level `env:`**, where only the `secrets` and `vars` contexts are legal — one illegal reference invalidates the whole file. Every push produced a 0-second failure that no PR check reported, and the reason surfaced only as `HTTP 422 … Unrecognized named-value: 'runner'` from `gh workflow run`. So run the non-publishing rehearsal (`gh workflow run publish.yml --ref <branch> -f publish=false`, or on `main` before the tag) as a standing first step of any release: it costs a minute and is the only exercise that path gets between releases |
| **Deleting `TEMP` from the env handed to a child does not remove it in the child** | 🧪 Measured while writing the issue #7 regressions: after the parent deleted `TEMP`/`TMP`/`TMPDIR` from the env object passed to `execFileSync`, the child still reported `'TEMP' in process.env === true` and `os.tmpdir()` equal to the real user temp (`TMP`/`TMPDIR` did stay absent). Setting them to `''` **does** propagate, and `os.tmpdir()` skips an empty value and falls back to an absolute default — so a test that needs "no temp variable" must set them empty rather than delete them, or assert the property on a pure helper instead (see `bench/results-dir.js` and its unit test in `test/bench.test.js`) |
| **A computed README badge can publish a claim with nothing behind it, and no check will notice** | 🧪 Measured 2026-10-11: the `Agent skills installs` badge pointed at `skills.sh/b/slow-stack/euthyna.svg`, which renders `resource not found` in red (`fill="#dd4343"`) while its own link is a `404` and the site's search API answers `{"skills":[],"count":0}` for `euthyna`. The listing never existed, and the badge sat in the README for 19 days from `eafd69d`. When touching the badge block, read each SVG's `aria-label` and each link's status: a badge with a hard-coded `color=` is decoration, not evidence. Full measurement: `### skills.sh` |
| **The registry refuses a second slug's submission of the version the first slug just took — and names a version the refused slug does not have** | 🧪 Measured 2026-10-21 on 0.9.0: `euthyna` landed, then `euthyna-en` was refused `Version 0.9.0 already exists`, while `verify euthyna-en --version 0.9.0` answered `Version not found` minutes later. Under `bash -e` that refusal ended the publish step, so the second edition was never submitted and the verify step — the only reader of a missing half — never ran. Walk the editions in one loop, treat a duplicate refusal as already-registered and continue, and let the verify step decide whether the listing arrived. Full measurement: `### ClawHub registry` |
| **A global npm command can be a shim with nothing behind it, and then block its own reinstall** | 🧪 Measured 2026-10-21: `euthyna --version` threw `MODULE_NOT_FOUND` because `%APPDATA%\npm\node_modules\euthyna` had been removed while its three shims (dated 2026-09-27) survived; `npm install -g` then fails `EEXIST` rather than overwrite files it holds no record of. Move the shims aside — do not delete — reinstall, and test the command rather than its presence: `euthyna help` must exit 0, and read the version out of the installed `package.json` |

---

Expand Down
Loading