Skip to content

fix(ci): disambiguate WASM bundle filenames to prevent tool/channel collision#964

Merged
zmanian merged 4 commits intostagingfrom
worktree-wit-versioned-hosting
Mar 12, 2026
Merged

fix(ci): disambiguate WASM bundle filenames to prevent tool/channel collision#964
zmanian merged 4 commits intostagingfrom
worktree-wit-versioned-hosting

Conversation

@henrypark133
Copy link
Copy Markdown
Collaborator

@henrypark133 henrypark133 commented Mar 11, 2026

Summary

  • Kind-prefix WASM bundle filenames (tool-slack-... vs channel-slack-...) in the CI build to prevent collisions when a tool and channel share the same name (e.g. slack, telegram)
  • Parse the kind prefix when patching registry manifests so each manifest gets the correct artifact URL and SHA256
  • Validate .kind field is tool or channel before using in paths/filenames
  • Filter non-WASM entries from checksums.txt before parsing to avoid noisy warnings

Root Cause

In build-wasm-extensions, the bundle slack-0.2.1-wasm32-wasip2.tar.gz was produced twice (once for the tool, once for the channel) — the second overwrote the first in target/wasm-bundles/. Then build-local-artifacts and update-registry-checksums patched both registry/tools/slack.json and registry/channels/slack.json with the surviving artifact's URL + SHA256, causing the tool installer to fail with "tar.gz archive does not contain 'slack-tool.wasm'".

Changes

  • build-wasm-extensions: Extract kind from manifest JSON, validate it, prefix bundle filename with kind
  • build-local-artifacts: Filter to WASM entries, parse kind prefix (cut -d'-' -f1), validate kind, target correct manifest
  • update-registry-checksums: Same filtering + kind-prefix parsing fix
  • installer.rs: Add #[derive(Debug)] to ExtractResult, add 4 regression tests for tool/channel name disambiguation
  • scripts/test-ci-artifact-naming.sh: 15 test cases verifying kind-prefixed filename parsing

Test plan

  • cargo fmt — clean
  • cargo clippy --all --benches --tests --examples --all-features — zero warnings
  • cargo test — all 18 installer tests pass (14 existing + 4 new)
  • scripts/test-ci-artifact-naming.sh — 15/15 pass
  • Next release build produces separate tool-slack-* and channel-slack-* artifacts

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings March 11, 2026 21:30
@github-actions github-actions bot added scope: ci CI/CD workflows scope: docs Documentation scope: dependencies Dependency updates size: M 50-199 changed lines risk: medium Business logic, config, or moderate-risk modules labels Mar 11, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical CI build issue where WASM bundle filenames would collide when a tool and a channel shared the same name, leading to incorrect artifact URLs and SHA256 checksums in registry manifests. The solution involves introducing a kind-prefix to WASM bundle filenames and updating the CI scripts to correctly parse these prefixes, ensuring unique and accurate artifact linking. Additionally, unnecessary WIT-versioned hosting changes were reverted from the installer.

Highlights

  • WASM Bundle Filename Disambiguation: Implemented kind-prefixing for WASM bundle filenames (e.g., "tool-slack-..." vs "channel-slack-...") in the CI build process to prevent collisions when a tool and channel share the same name.
  • Registry Manifest Parsing Logic: Modified the parsing logic for registry manifests to correctly use the kind prefix, ensuring each manifest receives the appropriate artifact URL and SHA256.
  • Installer Hosting Changes Reverted: Reverted WIT-versioned hosting changes in installer.rs, as the embedded catalog in v0.18.0+ already provides correct immutable URLs.
Changelog
  • CHANGELOG.md
    • Added entry for version 0.18.0 including merge requests and WASM artifact SHA256 checksum updates.
  • Cargo.lock
    • Updated the ironclaw package version from 0.17.0 to 0.18.0.
  • Cargo.toml
    • Updated the ironclaw package version from 0.17.0 to 0.18.0.
  • scripts/test-ci-artifact-naming.sh
    • Added a new shell script to test the kind-prefixed artifact filename parsing logic.
  • src/main.rs
    • Removed an unused ChannelSecretUpdater import from the global scope and re-added it within a specific cfg(unix) block where it is used.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/release.yml
Activity
  • The author performed cargo fmt, cargo clippy, and cargo test with zero warnings and all tests passing.
  • A new script, scripts/test-ci-artifact-naming.sh, was run, with all 15 test cases passing.
  • The author plans to verify that the next release build produces separate tool-slack-* and channel-slack-* artifacts.
  • The PR summary was generated using Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the release CI pipeline to avoid WASM bundle filename collisions between tools and channels that share the same registry filename (e.g. slack, telegram) by introducing a kind- prefix, and adds a small regression test script to validate the new parsing behavior.

Changes:

  • Prefix WASM bundle filenames with kind (tool-... vs channel-...) and update manifest patching logic accordingly in the release workflow.
  • Add scripts/test-ci-artifact-naming.sh to validate kind-prefixed filename parsing into the correct registry manifest path.
  • Bump crate version/changelog to 0.18.0 and adjust a cfg(unix) import in src/main.rs.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/main.rs Moves ChannelSecretUpdater import into the #[cfg(unix)] block to avoid non-unix import issues.
scripts/test-ci-artifact-naming.sh Adds a parsing regression test for kind-prefixed artifact filenames.
Cargo.toml Bumps package version to 0.18.0.
Cargo.lock Updates lockfile to reflect 0.18.0.
CHANGELOG.md Adds a 0.18.0 release section.
.github/workflows/release.yml Implements kind-prefixed bundling and kind-aware registry manifest patching to prevent collisions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -0,0 +1,58 @@
#!/bin/bash
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This script uses #!/bin/bash, but the other repo scripts consistently use #!/usr/bin/env bash for portability (e.g. scripts/build-all.sh, scripts/build-wasm-extensions.sh). Align the shebang here to match the project convention.

Suggested change
#!/bin/bash
#!/usr/bin/env bash

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in latest push — updated to #\!/usr/bin/env bash.

Comment on lines +4 to +15
set -euo pipefail

PASS=0
FAIL=0

assert_parse() {
local filename="$1" expected_kind="$2" expected_name="$3"
local kind name manifest

kind=$(echo "$filename" | cut -d'-' -f1)
name=$(echo "$filename" | sed "s/^${kind}-//" | sed 's/-[0-9].*-wasm32-wasip2\.tar\.gz$//')
manifest="registry/${kind}s/${name}.json"
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Most scripts cd to the repo root early (so they work when invoked from any working directory), but this script relies on relative paths like registry/... without changing directories. Consider adding a cd "$(git rev-parse --show-toplevel)" (or cd "$(dirname "$0")/..") near the top so it runs reliably.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in latest push — added cd "$(dirname "$0")/.." at the top.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively resolves the WASM bundle filename collision by prefixing them with their 'kind' (tool or channel). The changes to the CI scripts and the addition of a dedicated test script are well-thought-out. The refactoring in src/main.rs to localize a use statement is also a good improvement for code clarity. I have one suggestion to make the new test script's parsing logic even more robust.

local kind name manifest

kind=$(echo "$filename" | cut -d'-' -f1)
name=$(echo "$filename" | sed "s/^${kind}-//" | sed 's/-[0-9].*-wasm32-wasip2\.tar\.gz$//')
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.

medium

The current parsing logic for the extension name is a bit fragile. It uses a greedy sed pattern that could fail if an extension name contains a hyphen followed by a digit (e.g., my-tool-v2).

A more robust approach would be to use a single, more specific sed command with a capture group that explicitly matches a semantic version number. This would make the parsing logic less ambiguous and prevent potential mis-parsing of valid extension names.

Suggested change
name=$(echo "$filename" | sed "s/^${kind}-//" | sed 's/-[0-9].*-wasm32-wasip2\.tar\.gz$//')
name=$(echo "$filename" | sed -E 's/^'"$kind"'-(.+)-[0-9]+\.[0-9]+\.[0-9]+.*-wasm32-wasip2\.tar\.gz$/\1/')

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Intentionally not changing — this test script mirrors release.yml's parsing logic exactly (same sed pattern). Changing only the test would create divergence and false confidence. If the parsing ever needs to become more robust, both release.yml and this test should be updated together.

…ollision

When a tool and channel share the same name (e.g. slack, telegram), the
CI build produced identical bundle filenames, causing the second to
overwrite the first. Both manifests then pointed to the wrong binary.

Prefix bundle filenames with the extension kind (tool-slack-... vs
channel-slack-...) and parse the prefix when patching manifests, so each
manifest receives the correct artifact URL and SHA256.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@henrypark133 henrypark133 force-pushed the worktree-wit-versioned-hosting branch from ee8e7fd to 873698a Compare March 11, 2026 21:55
Regression tests for the CI artifact collision fix (PR #964). Verifies:
- extract_tar_gz rejects archives with wrong wasm name (the collision bug)
- Tool bundle extracts slack-tool.wasm correctly
- Channel bundle extracts slack.wasm correctly
- Tool and channel manifests install to separate directories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 11, 2026 22:37
@github-actions github-actions bot added size: L 200-499 changed lines and removed size: M 50-199 changed lines labels Mar 11, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +161 to +166
kind=$(echo "$filename" | cut -d'-' -f1)
name=$(echo "$filename" | sed "s/^${kind}-//" | sed 's/-[0-9].*-wasm32-wasip2\.tar\.gz$//')
url="https://github.com/nearai/ironclaw/releases/download/${RELEASE_TAG}/${filename}"

for manifest in registry/tools/${name}.json registry/channels/${name}.json; do
if [ -f "$manifest" ]; then
jq --arg sha "$sha256" --arg url "$url" \
'.artifacts["wasm32-wasip2"].sha256 = $sha | .artifacts["wasm32-wasip2"].url = $url' \
"$manifest" > "${manifest}.tmp" && mv "${manifest}.tmp" "$manifest"
echo "Patched $manifest with sha256=$sha256 url=$url"
fi
done
manifest="registry/${kind}s/${name}.json"
if [ -f "$manifest" ]; then
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The name extraction uses sed 's/-[0-9].*-wasm32-wasip2\.tar\.gz$//', which will strip too early if the extension file_stem contains a hyphen followed by a digit (e.g. tool-google-2fa-0.2.0-... would parse as google instead of google-2fa). Consider parsing by first removing the fixed suffix, then removing the version starting at the last -[0-9] segment (e.g. via bash parameter expansion or a regex anchored to the last match) so hyphen+digit in names remains supported.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Valid theoretical concern, but no current or planned extension names contain -digit patterns that aren't version numbers. The sed pattern is intentionally identical across build-local-artifacts, update-registry-checksums, and the test script — they must stay in sync. If we ever add an extension like google-2fa, we'd need to update the parsing everywhere (likely switching to a more structured approach). Tracked but not blocking.

Comment on lines +165 to +167
manifest="registry/${kind}s/${name}.json"
if [ -f "$manifest" ]; then
jq --arg sha "$sha256" --arg url "$url" \
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

kind is taken directly from the checksum filename and then interpolated into registry/${kind}s/.... If kind is empty/unexpected, this silently skips patching and can produce incorrect artifact URLs/SHA256s for a release. Consider validating kind is exactly tool or channel (and failing the job or emitting an explicit error) before using it to build paths.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch — will add validation that kind is tool or channel before using it in paths.

file_stem=$(basename "$manifest" .json)
# kind: "tool" or "channel" — used as bundle filename prefix to avoid
# collisions when a tool and channel share the same file_stem (e.g. slack).
kind=$(jq -r '.kind' "$manifest")
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

kind=$(jq -r '.kind' ...) is used as a bundle filename prefix. If .kind is missing/typoed, this will generate bundles like null-... and later checksum patching won’t update the intended manifest. Consider checking kind is tool or channel (and erroring out) before continuing for each manifest.

Suggested change
kind=$(jq -r '.kind' "$manifest")
kind=$(jq -r '.kind' "$manifest")
if [ "$kind" != "tool" ] && [ "$kind" != "channel" ]; then
echo "::error::Manifest '$manifest' has invalid or missing .kind ('$kind'); expected 'tool' or 'channel'"
exit 1
fi

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch — will add this validation. If .kind is missing or invalid, we should fail loudly rather than produce a broken bundle.

# Parse kind-prefixed filename: "tool-slack-0.2.1-wasm32-wasip2.tar.gz"
# → kind=tool, name=slack
kind=$(echo "$filename" | cut -d'-' -f1)
name=$(echo "$filename" | sed "s/^${kind}-//" | sed 's/-[0-9].*-wasm32-wasip2\.tar\.gz$//')
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Same parsing issue as earlier in the workflow: name=$(... | sed 's/-[0-9].*-wasm32-wasip2\.tar\.gz$//') can drop parts of names that contain -<digit> (e.g. google-2fa). Consider using a parsing method that removes the version starting at the last -[0-9] occurrence after stripping the fixed suffix, so filenames remain reversible for all valid manifest names.

Suggested change
name=$(echo "$filename" | sed "s/^${kind}-//" | sed 's/-[0-9].*-wasm32-wasip2\.tar\.gz$//')
base="${filename#${kind}-}"
base="${base%-wasm32-wasip2.tar.gz}"
name="${base%-[0-9]*}"

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Same as the earlier comment — the sed pattern is intentionally kept in sync across all three locations. No current extension names trigger this edge case.

henrypark133 and others added 2 commits March 11, 2026 16:07
- Validate .kind is "tool" or "channel" before using in build-wasm-extensions (hard error)
- Filter checksums.txt to *-wasm32-wasip2.tar.gz entries before parsing, avoiding noisy warnings from binary artifact entries in build-local-artifacts
- Add kind validation with warning+skip in both checksum-parsing loops

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 11, 2026 23:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@nickpismenkov nickpismenkov left a comment

Choose a reason for hiding this comment

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

LGTM!

@zmanian zmanian merged commit 81f7b64 into staging Mar 12, 2026
13 checks passed
@zmanian zmanian deleted the worktree-wit-versioned-hosting branch March 12, 2026 00:02
@ironclaw-ci ironclaw-ci bot mentioned this pull request Mar 12, 2026
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
…ollision (nearai#964)

* fix(ci): disambiguate WASM bundle filenames to prevent tool/channel collision

When a tool and channel share the same name (e.g. slack, telegram), the
CI build produced identical bundle filenames, causing the second to
overwrite the first. Both manifests then pointed to the wrong binary.

Prefix bundle filenames with the extension kind (tool-slack-... vs
channel-slack-...) and parse the prefix when patching manifests, so each
manifest receives the correct artifact URL and SHA256.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(registry): add installer tests for tool/channel name disambiguation

Regression tests for the CI artifact collision fix (PR nearai#964). Verifies:
- extract_tar_gz rejects archives with wrong wasm name (the collision bug)
- Tool bundle extracts slack-tool.wasm correctly
- Channel bundle extracts slack.wasm correctly
- Tool and channel manifests install to separate directories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): add kind validation and filter non-WASM checksum entries

- Validate .kind is "tool" or "channel" before using in build-wasm-extensions (hard error)
- Filter checksums.txt to *-wasm32-wasip2.tar.gz entries before parsing, avoiding noisy warnings from binary artifact entries in build-local-artifacts
- Add kind validation with warning+skip in both checksum-parsing loops

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: fix rustfmt formatting in installer tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: ci CI/CD workflows scope: dependencies Dependency updates scope: docs Documentation size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants