Worker release

July 3, 2026 · View on GitHub

Sources of truth: .github/workflows/create-tag.yml, .github/workflows/release.yml, .github/workflows/release-lsp-vscode.yml, .github/workflows/_rust-binary.yml, .github/workflows/_container.yml, .github/workflows/_bundle.yml, .github/workflows/_publish-registry.yml, .github/scripts/parse_release_tag.py. On conflict, the workflow wins — update this doc.

Operational SOP for cutting a worker version, monitoring the pipeline, and verifying registry publish. One-time wiring is in new-worker.md §6.

Prerequisites

  • Branch: Create Tag requires main (preflight enforces it).
  • Access: GitHub Actions on this repo; org secrets configured (do not paste values):
    • III_CI_APP_ID / III_CI_APP_PRIVATE_KEY — bot commit + tag push in Create Tag
    • WORKERS_REGISTRY_API_KEYPOST /publish and POST /w/<worker>/skills
  • Worker wired: create-tag.yml options + release.yml tag pattern (see new-worker.md §6).
  • Local green: lint + tests for the worker; Rust binary: --manifest JSON valid.

Standard release (happy path)

1. Create Tag

Actions → Create Tag:

InputMeaning
WorkerFolder name (must be in workflow options)
Bumppatch / minor / major
Registry taglatest or next — channel for iii worker add resolution

The workflow:

  1. Bumps version in the worker manifest (Cargo.toml, package.json, …).
  2. Commits chore(<worker>): bump to vX.Y.Z to main.
  3. Creates and pushes an annotated tag <worker>/vX.Y.Z with registry-tag: <latest|next> in the tag message.

2. Release pipeline

Tag push triggers Release (release.yml):

flowchart LR
  createTag[Create Tag] -->|"tag worker/vX.Y.Z"| setupJob[setup]
  setupJob --> ghRelease[create GitHub Release]
  ghRelease --> buildBinary["binary: _rust-binary.yml"]
  ghRelease --> buildImage["image: _container.yml"]
  ghRelease --> buildBundle["bundle: _bundle.yml"]
  buildBinary --> publishJob[_publish-registry.yml]
  buildImage --> publishJob
  buildBundle --> publishJob
  publishJob --> postPublish["POST /publish + skills"]
StageJobOutput
setupParse tag + iii.worker.yaml; detect web bundle / smoke opt-outworker, version, deploy, targets, …
create-releaseGitHub Release shellRelease page for the tag
binary-build_rust-binary.ymlPer-target .tar.gz / .zip + .sha256 on the Release
container-build_container.ymlMulti-arch image at ghcr.io/<owner>/<worker>
bundle-build_bundle.yml<worker>.tar.gz + .sha256 on the Release
publish_publish-registry.ymlRegistry manifest + optional skills upload

deploy from iii.worker.yaml selects exactly one build job.

3. What publish does

_publish-registry.yml:

  1. Boots a clean iii engine (workers: []).
  2. Starts the released artifact (mode from manifest_version.py deploy-mode):
    • release-binary — downloads Linux gnu tarball from the GitHub Release
    • release-bundle — extracts <worker>.tar.gz, runs node ./index.mjs
    • iii-addiii worker add ./<worker> (non-binary/bundle deploys with runtime/scripts.start, e.g. image workers)
    • cargo-runcargo run from source (remaining Rust workers)
  3. Uses config.collect.yaml when present (sidecar-free boot).
  4. Collects function + trigger interface (120 s timeout); asserts non-empty.
  5. Resolves release assets into payload.json.
  6. POST /publish to https://api.workers.iii.dev.
  7. POST /w/<worker>/skills when skills/*.md exist (skipped when absent).

Workers with interface_smoke: false skip the entire publish job.

4. Registry tag semantics

ChannelTypical use
latestDefault; what most iii worker add installs resolve
nextPre-release / risky channel; safer for first publish

The channel is stored in the annotated tag message (registry-tag:). release.yml refetches the annotated tag for this reason. Lightweight tags lose the channel and default to latest.

Variants

Re-run a failed release

Actions → Releaseworkflow_dispatch → enter the existing tag (e.g. session-manager/v0.1.0). No new tag or version bump needed. Concurrency group release-${{ github.ref }} serializes per tag.

Prerelease

Create Tag cannot produce prerelease suffixes. Push a manual annotated tag:

<worker>/vX.Y.Z-beta.1

With tag message including registry-tag: next. Marks the GitHub Release as prerelease; still builds and publishes (unless interface_smoke: false).

Dry run

Tag shape: <worker>/vX.Y.Z-dry-run.1 (parsed by parse_release_tag.py).

  • Runs the full build matrix
  • Skips GitHub Release asset upload and registry publish
  • Useful to validate a new worker's pipeline before v0.1.0

Skills-only update

Actions → Publish worker skills — worker must be in ALLOWED_WORKERS (parse_publish_workers_input.py). No version bump; updates skill markdown on the registry channel you pick.

LSP VS Code extension

lsp-vscode uses release-lsp-vscode.yml (VS Code extension packaging, separate tag pattern lsp-vscode/v*). The Marketplace/OpenVSX package name remains iii-lsp.

The extension release workflow packages the VSIX once and then publishes it via separate jobs:

JobExternal side effect
publish-vscodePublishes the VSIX to VS Code Marketplace
publish-openvsxPublishes the same VSIX to OpenVSX
github-releaseUploads the VSIX to the GitHub Release

If only one target fails, do not create another version bump. Either use GitHub Actions "Re-run failed jobs" for the same run, or dispatch Release LSP VS Code manually with the existing tag and the failed target:

InputExample
taglsp-vscode/v0.2.7
publish_targetopenvsx, vscode-marketplace, or github-release

Use publish_target=all only for the first publish attempt or when all targets are known to be safe to run again.

Pre-bumped manifest

If a merged PR already set the manifest version (e.g. a breaking change that names its own release), use Create Tag with Bump = none: it releases the manifest version as-is, skips the bump commit, and still refuses existing tags.

Troubleshooting

SymptomLikely causeFix
Tag pushed, nothing ranMissing '<worker>/v*' in release.ymlAdd pattern (§6 in new-worker.md); dispatch Release manually meanwhile
setup failsInvalid tag shape or bad iii.worker.yamlTag must match worker/vVERSION; deploy must be binary|image|bundle
binary-build fails on one targetCross-compile issueConsider targets: subset in iii.worker.yaml (shell is POSIX-only); other targets still upload (fail-fast: false)
interface collection times outWorker crashes on clean runner (#104 class)Ship config.collect.yaml; check iii-engine.log, worker-<name>.log in the job
Worker exits before collectionMissing parent dir, sidecar, bad default pathSame as above; reproduce locally with no data/ dir
artifact resolution 404Build job didn't upload for that targetCheck GitHub Release assets for the tag
publish HTTP non-200Registry rejection or bad payloadResponse body printed in job log; verify WORKERS_REGISTRY_API_KEY
publish skipped entirelyinterface_smoke: falseExpected for stdio/discovery-only workers

On failure, publish dumps iii-engine.log and worker-<worker>.log (last 200 lines).

Rollback

There is no unpublish. Recovery:

  1. Fix the issue on main.
  2. Cut a new patch via Create Tag (registry latest moves forward).
  3. Use registry-tag: next when uncertain before promoting to latest.

GitHub Release assets for the bad version remain (immutable history).

Post-release verification

On a clean host:

iii worker add <name>
iii worker info <name>

Confirm:

  • Version matches the tag you cut.
  • Function and trigger types match expectations.
  • GitHub Release has complete assets (per-target archives + .sha256 for binary/bundle deploys).

Announce & organize

Slack announcement is automatic: the terminal announce job in release.yml posts 🚀 <worker> vX.Y.Z to #worker-releases for every successful non-dry-run release. SLACK_BOT_TOKEN is org-level (the same bot as the iii engine release pipeline); the bot must be invited to #worker-releases. The GitHub release-notes body is posted as a thread reply under the announcement. Ticket association rides on PR titles — (MOT-##) type: description — enforced by the PR Linear Check workflow (no-ticket label for bump/typo/CI-only PRs).

After a release session — any number of tags — run /release-sync in Claude Code from the repo root. Same-day tags form one wave = one Linear document on team iii (Release YYYY-MM-DD) holding the combined per-worker note, with shipped MOT-### issues carrying a release · YYYY-MM-DD label. Catch-up semantics: tags released without running the skill are picked up on the next run. Conventions and setup checklist: Release workflow — workers.