name: updating-node description: Updates Node.js submodule to latest stable tag, syncs .node-version, regenerates patches via autonomous agent, bumps node-smol cache. Use for new Node.js releases, security patches, or API updates. user-invocable: true allowed-tools: Task, Bash(pnpm:), Bash(npm:), Bash(git:), Bash(node:), Bash(rg:), Bash(grep:), Bash(find:), Bash(ls:), Bash(cat:), Bash(head:), Bash(tail:), Bash(wc:), Bash(diff:*), Read, Edit, Skill---
updating-node
Spawn an autonomous agent that updates Node.js submodule to latest stable, syncs .node-version, regenerates patches, and validates everything.
- Submodule:
packages/node-smol-builder/upstream/node(nodejs/node) - Tag format:
vX.Y.Z(stable only, exclude rc/alpha/beta) - Cache bumps:
node-smol— see "When to bump node-smol cache" below - Creates: Two commits (version update + patch regeneration)
When to bump node-smol cache
The node-smol entry in .github/cache-versions.json keys Docker layer
caches and the GHA build cache. Bump it whenever a build input changes,
not only when Node itself bumps. Triggers:
- Node.js submodule SHA changes (new tag).
packages/node-smol-builder/docker/Dockerfile.*changes (apt/apk packages, rustup install, base image bumps).additions/source-patched/**/*changes (new source-patched files).patches/source-patched/**/*changes (patch series adds/removes/edits).scripts/binary-released/**/*.mtschanges that alterconfigureFlags, build env vars, or output paths (e.g. adding--v8-enable-temporal-support).external-tools.jsonchanges that flow into the Dockerfile via.build-context/registry-tools.json(pnpm pin, rust pin, sfw pin).
If unsure: bumping is cheap (~30 min cold build), not bumping when you should produces silent stale-cache hits that are very expensive to debug.
Process
Phase 1: Validate
Clean working directory, verify submodule exists, read current .node-version.
Phase 2: Spawn Agent
Spawn a Task agent with the full workflow from reference.md. The agent:
- Fetches tags, identifies latest stable
- Updates submodule to new tag
- Updates
.node-versionto match - Bumps
node-smolcache version - Commits version update
- Invokes
regenerating-patchesskill to regenerate all patches - Validates build and tests (skip in CI)
- Creates patch regeneration commit
See reference.md for the complete agent prompt template.
Phase 3: Post-Update Skills (skip in CI)
In interactive mode, dispatch in this dependency order — curl and LIEF have no prereqs, so they parallelize; everything else is sequential:
- curl + lief (parallel) — both link only against the toolchain (Rust + Emscripten/native), no socket-btm artifacts upstream. Dispatch both, wait for both releases to land.
- stubs — consumes curl + lief; produces platform stubs that binsuite + node-smol SEA-inject.
- binsuite — consumes stubs (+ curl, lief).
- temporal-infra — invokes
/updating-temporal-infrato refresh the parity reference + audit the C++ port for drift. Short- circuits ifboa-dev/temporalhasn't cut a new tag since the last run (no commit, cascade proceeds). When it DOES move, the C++ port catches up before node-smol consumes the changes viaadditions/source-patched/. - node-smol — consumes stubs + binsuite + curl + lief + the refreshed temporal C++ port; the final layer.
Adjacent vendor syncs (independent of the chain): updating-fast-webstreams,
updating-zstd — can run any time.
Why the order matters: node-smol embeds the stub-injected curl
binary plus the LIEF library AND consumes the temporal C++ port via
additions/source-patched/; dispatching node-smol before its
prerequisites cascade leaves it building against stale dependencies
and surfaces "fixed" issues in the wrong layer.
Coupling is one-way: /updating-node exercises
/updating-temporal-infra so every Node bump has a current parity
reference. A standalone /updating-temporal-infra run (boa-dev/temporal
cuts a tag while Node is current) does NOT drag in a Node rebuild.
Phase 4: Report
Version change, commits created, patch status, post-update results.
Phase 5: Validate via CI dispatch (gh workflow run)
Once the prerequisite skills above land their commits, validate the
full chain by dispatching node-smol.yml.
🚨 Dispatch policy
The release-workflow-guard hook risk-tiers each dispatch:
- Verifiable dry-run (
-f dry-run=true+ workflow declares the input) — always allowed. - GitHub-release-only workflow (no
npm/pnpm/yarn publishin the YAML; onlygh release create/ release action) — allowed live. node-smol, stubs, curl, LIEF, binsuite, etc. all qualify. Recovery for a bad release:gh release delete <tag> --cleanup-tag --yes. - npm-publishing workflow — always blocked. The user runs these themselves.
- Force-prod override (
-f publish=trueetc.) — always blocked even on a GH-only workflow, since the override may flip in an npm-publish branch.
Pre-dispatch checklist (live releases only)
🚨 Before dispatching a non-dry-run release build:
- Bump the cache version in
.github/cache-versions.jsonfor the artifact you're releasing. Skipping this re-publishes from a stale cache — the new release is byte-identical to the old one. - Check live release count. Cap is 2 per artifact: keep
the current release plus one prior as a safety net. If a 3rd
would land, delete the oldest first:
gh release list --json tagName,createdAt --limit 50 \ | jq -r '.[] | select(.tagName | startswith("stubs-")) | .tagName' \ | tail -n +3 \ | xargs -I {} gh release delete {} --cleanup-tag --yes - Dispatch only after Phase 3 completes. Skipping Phase 3 means node-smol builds against stale stubs/curl/LIEF and the failures surface in the wrong layer.
Dry-run policy still applies for validation dispatches —
when you just want to see whether the source tree compiles. Pass
-f dry-run=true and the hook lets it through.
🚨 Monitor policy: stop on first failure
When polling a node-smol run via gh run view --json jobs, the goal
is not "wait for all 8 platform jobs to finish" — it's "find the
first failure, stop, and fix." Each platform burns ~30–60 minutes of
runner time; letting 7 jobs finish after one already failed wastes
the runner pool and your wall clock.
RUN_ID=<dispatched-run>
while true; do
json=$(gh run view "$RUN_ID" --repo SocketDev/socket-btm --json status,conclusion,jobs)
status=$(echo "$json" | jq -r '.status')
failed=$(echo "$json" | jq -r '.jobs[] | select(.conclusion == "failure") | .name' | head -1)
if [ -n "$failed" ]; then
echo "FAIL: $failed — cancelling run + diagnosing"
gh run cancel "$RUN_ID" --repo SocketDev/socket-btm
break
fi
[ "$status" = "completed" ] && break
sleep 90
done
When a failure surfaces:
- Cancel the run immediately —
gh run cancel <id>. Don't let sibling jobs finish; their outcome is moot. - Pull the actual compile error —
gh api repos/.../actions/jobs/$JOB/logs | grep -E "error:|FAILED:". - Fix forward in main — patch the source / regenerate the affected patch, commit, push.
- Redispatch the same workflow only after the fix lands.
Why fail-fast matters here: node-smol failures are usually mechanical (an API rename, a header move, a patch hunk drift). The first failing platform tells you the issue; the other 7 will hit the same mechanical issue on the next stage anyway. Wait for them only if you need cross-platform divergence data, which is rare.