Repository navigation
[Bug]: workflow add <url> installs workflow.yml with mode 0600, while a catalog install uses the project file mode #5077
Description
Bug Description
When specify workflow add is given a direct URL to a workflow YAML file, it downloads the file into a tempfile.NamedTemporaryFile, which has mode 0600 (src/specify_cli/workflows/command_add.py:400). It then copies that file's mode onto the installed workflow.yml (command_add.py:105 and :166). A catalog install of the same file uses the normal project file mode, 0666 masked by the umask (src/specify_cli/workflows/_commands.py:441). The same workflow therefore ends up readable only by its owner when installed from a URL.
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 the files from a local 127.0.0.1 HTTP server.
import functools, http.server, json, os, stat, subprocess, tempfile, threading
from pathlib import Path
os.umask(0o022)
wf = ('schema_version: "1.0"\nworkflow:\n id: "{}"\n name: "Demo"\n'
' version: "1.0.0"\nsteps:\n - id: s1\n type: shell\n run: "true"\n')
proj = Path(tempfile.mkdtemp())
(proj / ".specify").mkdir()
site = proj / "site"
site.mkdir()
(site / "catwf.yml").write_text(wf.format("catwf"))
(site / "urlwf.yml").write_text(wf.format("urlwf"))
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]}"
(site / "catalog.json").write_text(json.dumps({"workflows": {"catwf": {
"id": "catwf", "name": "Demo", "version": "1.0.0", "url": base + "/catwf.yml"}}}))
env = {**os.environ, "SPECKIT_WORKFLOW_CATALOG_URL": base + "/catalog.json"}
subprocess.run(["specify", "workflow", "add", "catwf"], cwd=proj, env=env, check=True, capture_output=True)
subprocess.run(["specify", "workflow", "add", base + "/urlwf.yml"], cwd=proj, env=env, check=True, capture_output=True)
for wid in ("catwf", "urlwf"):
mode = (proj / ".specify/workflows" / wid / "workflow.yml").stat().st_mode
print(wid, oct(stat.S_IMODE(mode)))Expected Behavior
Both installs get the project file mode, 0o644 under umask 022. tests/specify_cli/workflows/test_command_add.py:2484-2528 asserts 0644 for a fresh catalog install, and the _stage_workflow_file docstring (_commands.py:441) describes "the normal project-file mode" for downloaded files.
Actual Behavior
catwf 0o644
urlwf 0o600
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
Additional Context
Low severity. Staging the URL download with use_project_file_mode=True, as the catalog path does, would make the two consistent.
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