From 95794080103dc18651d687667d016fb8b245a878 Mon Sep 17 00:00:00 2001 From: Guilherme Beira Date: Mon, 4 May 2026 11:00:33 -0300 Subject: [PATCH 1/2] fix(ci): bump iii to v0.11.6-next.1 and refetch annotated release tag - _publish-registry.yml: pin iii install to v0.11.6-next.1 with --next. The previous v0.11.5 pin (added in #67) didn't surface external workers correctly in `engine::workers::list`, so `collect_worker_interface.py` raised `ValueError: expected exactly one worker matching 'image-resize', found 0` even when the worker was alive and connected. iii-hq/iii#1598 traced the same symptom on engine workers to PR iii-hq/iii#1585 landing in 0.11.6. - release.yml: actions/checkout@v4 fetches all tags first, then re-fetches the target SHA with `--no-tags +SHA:refs/tags/`, which silently overwrites the annotated tag with a lightweight one and drops the message. Add an explicit `git fetch origin +refs/tags/:refs/tags/` so `git tag -l --format=%(contents)` can still read the `registry-tag:` line and route the publish to next/latest as intended. --- .github/workflows/_publish-registry.yml | 4 ++-- .github/workflows/release.yml | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_publish-registry.yml b/.github/workflows/_publish-registry.yml index 6d297d2..b32b925 100644 --- a/.github/workflows/_publish-registry.yml +++ b/.github/workflows/_publish-registry.yml @@ -54,11 +54,11 @@ jobs: - name: Install iii CLI env: - VERSION: '0.11.5' + VERSION: '0.11.6-next.1' run: | set -euo pipefail curl -fsSL https://install.iii.dev/iii/main/install.sh -o /tmp/install-iii.sh - sh /tmp/install-iii.sh + sh /tmp/install-iii.sh --next { echo "$HOME/.local/bin" echo "$HOME/.iii/bin" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75b4690..bbcc002 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,6 +48,15 @@ jobs: fetch-depth: 0 fetch-tags: true + - name: Refetch annotated tag + env: + RAW_TAG: ${{ inputs.tag || github.ref_name }} + run: | + set -euo pipefail + if [[ -n "$RAW_TAG" ]]; then + git fetch origin "+refs/tags/${RAW_TAG}:refs/tags/${RAW_TAG}" + fi + - name: Install pyyaml run: pip install --quiet pyyaml From fc1e0a817a323f59dd80f832ea80e175d681a8e9 Mon Sep 17 00:00:00 2001 From: Guilherme Beira Date: Mon, 4 May 2026 16:16:39 -0300 Subject: [PATCH 2/2] fix(ci): fall back to namespace match when worker has no name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Rust SDK's register_worker(url, opts) doesn't take a worker name, so external Rust workers (image-resize, mcp, proof, iii-lsp) register with id= and name=null. Matching by `name == worker_name` always failed on these, even with the worker alive and connected. Add a fallback that looks for a worker whose registered functions are namespaced under the worker dir's snake_case (image-resize → image_resize::*). If exactly one worker matches, use it. On failure, include the workers list summary in the error so future regressions are debuggable from the log. Direct name/id match still wins so the iii-add path (Node/Python workers with explicit names) is unaffected. --- .github/scripts/build_publish_payload.py | 31 +++++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/scripts/build_publish_payload.py b/.github/scripts/build_publish_payload.py index 8aad641..06b4605 100644 --- a/.github/scripts/build_publish_payload.py +++ b/.github/scripts/build_publish_payload.py @@ -126,6 +126,31 @@ def _normalize_registry_trigger(trigger: dict[str, Any]) -> dict[str, Any]: } +def _match_worker(workers: list[dict[str, Any]], worker_name: str) -> dict[str, Any]: + by_name = [w for w in workers if w.get("name") == worker_name or w.get("id") == worker_name] + if len(by_name) == 1: + return by_name[0] + + namespace = worker_name.replace("-", "_") + by_namespace = [ + w + for w in workers + if any( + isinstance(fid, str) and fid.startswith(f"{namespace}::") + for fid in (w.get("functions") or []) + ) + ] + if len(by_namespace) == 1: + return by_namespace[0] + + summary = [{"id": w.get("id"), "name": w.get("name")} for w in workers] + raise ValueError( + f"could not match worker {worker_name!r}: " + f"{len(by_name)} by name/id, {len(by_namespace)} by namespace {namespace!r}, " + f"workers={summary}" + ) + + def normalize_worker_interface( *, worker_name: str, @@ -134,11 +159,9 @@ def normalize_worker_interface( triggers_json: dict[str, Any] | None = None, ) -> dict[str, list[dict[str, Any]]]: workers = _extract_array(workers_json, "workers") - matches = [w for w in workers if w.get("name") == worker_name or w.get("id") == worker_name] - if len(matches) != 1: - raise ValueError(f"expected exactly one worker matching {worker_name!r}, found {len(matches)}") + worker = _match_worker(workers, worker_name) - worker_function_ids = matches[0].get("functions") or [] + worker_function_ids = worker.get("functions") or [] if not isinstance(worker_function_ids, list): raise ValueError("worker `functions` must be an array")