Repository navigation
[Bug]: workflow update rejects an archive catalog release when its version has leading zeros (for example CalVer 2024.02.01) #5075
Description
Bug Description
specify workflow update normalises the installed and catalog versions with packaging before passing them on (src/specify_cli/workflows/command_update.py:88-92), so 2024.02.01 becomes 2024.2.1. Such versions are valid workflow versions, because the format is \d+\.\d+\.\d+ (src/specify_cli/workflows/engine.py:132). When the catalog URL is a YAML file, the downloaded version is compared with versions_match, which compares Version objects, and the update succeeds. When the catalog URL is an archive (a workflow package), _install_workflow_package compares plain strings (src/specify_cli/workflows/_commands.py:761 and :828-832). "2024.02.01" != "2024.2.1", so the update fails even though the package matches the catalog.
Steps to Reproduce
Save the script below as repro.py in a fresh clone (after uv sync) and run uv run python -I repro.py. It serves a catalog and a zip package from a local 127.0.0.1 HTTP server.
import functools, http.server, io, json, os, subprocess, tempfile, threading, zipfile
from pathlib import Path
def wf(ver):
return (f'schema_version: "1.0"\nworkflow:\n id: "calwf"\n name: "Cal WF"\n'
f' version: "{ver}"\nsteps:\n - id: s1\n type: shell\n run: "true"\n')
proj = Path(tempfile.mkdtemp())
site = proj / "site"
site.mkdir()
class Quiet(http.server.SimpleHTTPRequestHandler):
def log_message(self, *args):
pass
handler = functools.partial(Quiet, directory=str(site))
srv = http.server.ThreadingHTTPServer(("127.0.0.1", 0), handler)
threading.Thread(target=srv.serve_forever, daemon=True).start()
base = f"http://127.0.0.1:{srv.server_address[1]}"
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as z:
z.writestr("workflow.yml", wf("2024.02.01"))
z.writestr("README.md", "companion file")
(site / "calwf.zip").write_bytes(buf.getvalue())
(site / "catalog.json").write_text(json.dumps({"workflows": {"calwf": {
"id": "calwf", "name": "Cal WF", "version": "2024.02.01", "url": base + "/calwf.zip"}}}))
wdir = proj / ".specify" / "workflows" / "calwf"
wdir.mkdir(parents=True)
(wdir / "workflow.yml").write_text(wf("2024.01.15"))
(proj / ".specify/workflows/workflow-registry.json").write_text(json.dumps({
"schema_version": "1.0", "workflows": {"calwf": {
"name": "Cal WF", "version": "2024.01.15", "description": "",
"source": "catalog", "catalog_name": "env-override", "url": base + "/calwf.zip"}}}))
env = {**os.environ, "SPECKIT_WORKFLOW_CATALOG_URL": base + "/catalog.json"}
r = subprocess.run(["specify", "workflow", "update", "calwf"], cwd=proj, env=env,
input="y\n", capture_output=True, text=True)
print(r.stdout, r.stderr, f"exit={r.returncode}", sep="")
reg = json.loads((proj / ".specify/workflows/workflow-registry.json").read_text())
print("registry version:", reg["workflows"]["calwf"]["version"])Expected Behavior
The update succeeds, exits 0 and records version 2024.02.01, as it does when the same catalog entry points at a .yml URL. The docstring at src/specify_cli/workflows/_commands.py:1116 says expected_version "rejects a downloaded workflow whose version does not match the catalog version"; here the downloaded version does match.
Actual Behavior
The update fails and the registry keeps the old version:
🔄 Checking for updates...
Updates available:
• calwf: 2024.1.15 → 2024.2.1
Update these workflows? [y/N]:
Error: Downloaded workflow version (2024.02.01) does not match the catalog
version (2024.2.1).
Failed to update: calwf
exit=1
registry version: 2024.01.15
Specify CLI Version
1.2.4.dev0 (main at 27d14e2)
AI Agent
Not applicable
Operating System
Linux 6.18 (Ubuntu container)
Python Version
Python 3.12.3
Error Logs
Error: Downloaded workflow version (2024.02.01) does not match the catalog
version (2024.2.1).
Additional Context
The expected_installed_version check in the same function (_commands.py:828-832) uses the same exact string comparison, so it would reject the update for the same reason. Comparing both with versions_match, as the YAML path does, would fix it.
Also reproduced on macOS (Darwin 25.6, arm64) with Python 3.14.4 and uv run python -I repro.py against 27d14e2; the output matches the above.
AI Disclosure
Found and reproduced with Claude Code (model: Claude Opus 4.8), run autonomously under human supervision. The human reporter re-ran every reproduction above before filing.
Activity