{"record":{"id":"251d951d2b4c0859","repo":"github/spec-kit","slug":"invalid-extension-registry-registry-not-a-regul","errorCode":null,"errorMessage":"Invalid extension registry {registry}: not a regular file","messagePattern":"Invalid extension registry (.+?): not a regular file","errorType":"exception","errorClass":"TemplateResolutionError","httpStatus":null,"severity":"error","filePath":"scripts/python/common.py","lineNumber":248,"sourceCode":"        except Exception:\n            pass\n    try:\n        return sorted(\n            p.name\n            for p in presets_dir.iterdir()\n            if p.is_dir() and _is_safe_component(p.name)\n        )\n    except OSError:\n        return []\n\n\ndef _sorted_extension_ids(extensions_dir: Path) -> list[str]:\n    registry = extensions_dir / \".registry\"\n    registered_ids: set[str] = set()\n    extensions: dict[object, object] = {}\n    if os.path.lexists(registry):\n        if not registry.is_file():\n            raise TemplateResolutionError(\n                f\"Invalid extension registry {registry}: not a regular file\"\n            )\n        try:\n            data = json.loads(registry.read_text(encoding=\"utf-8\"))\n        except (OSError, UnicodeError, json.JSONDecodeError) as exc:\n            raise TemplateResolutionError(\n                f\"Failed to parse extension registry {registry}: {exc}\"\n            ) from exc\n        if not isinstance(data, dict):\n            raise TemplateResolutionError(\n                f\"Invalid extension registry {registry}: root must be a mapping\"\n            )\n        raw_extensions = data.get(\"extensions\", {})\n        if not isinstance(raw_extensions, dict):\n            raise TemplateResolutionError(\n                f\"Invalid extension registry {registry}: \"\n                \"'extensions' must be a mapping\"\n            )","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/scripts/python/common.py#L230-L266","documentation":"The streaming branch of the downloader: the response either had no (or unparseable/lying) Content-Length, and while reading in _DOWNLOAD_CHUNK_SIZE (64 KiB) chunks the running total exceeded max_bytes (5 MiB). This enforces the cap even when the header is absent or understates the body.","triggerScenarios":"`workflow add <url>` where the server omits Content-Length (chunked transfer) or lies about it, and the actual body exceeds 5 MiB; streaming endpoints that keep sending data; gzip-encoded responses whose decoded size balloons past the limit.","commonSituations":"Chunked transfer from CDNs/serverless endpoints without Content-Length; misconfigured proxies stripping or falsifying headers; URLs that redirect to a login/download page of unbounded size; archives or generated bundles served without metadata.","solutions":["Fetch the URL manually (curl -sL <url> | wc -c) to see the true payload size and fix the source URL to point at the workflow YAML itself","Ensure the URL serves the raw workflow file with accurate metadata, not a dynamically generated page","If the workflow genuinely needs more than 5 MiB, reduce it (externalize assets) or install it locally from a file path instead of HTTP","Pre-validate with a manual size check before invoking the CLI in automated pipelines"],"exampleFix":"# before\nspecify workflow add https://cdn.example.com/pack?page=all  # chunked, unbounded\n\n# after\nspecify workflow add https://raw.example.com/workflows/my-workflow.yaml\n# or install from a local file that you have size-checked","handlingStrategy":"validation","validationCode":"import urllib.request\n\nMAX = 5 * 1024 * 1024\nreq = urllib.request.Request(url, method='GET', headers={'Range': f'bytes=0-{MAX}'})\nwith urllib.request.urlopen(req) as r:\n    data = r.read(MAX + 1)\nif len(data) > MAX:\n    raise ValueError(f'body exceeds {MAX} bytes even before full download')","typeGuard":null,"tryCatchPattern":"try:\n    data = download_workflow_source(url)\nexcept ValueError as e:\n    if 'workflow size limit' in str(e):\n        # check true size, then fix or replace the source URL\n        raise SystemExit(f'actual payload too large: {e}') from e\n    raise","preventionTips":["Prefer servers/URLs that provide accurate Content-Length for workflow files","Avoid chunked/dynamic endpoints as workflow sources","curl -sL <url> | wc -c to verify real size before wiring automation","Install oversized-but-legit workflows from a local file path instead"],"tags":["network","download","size-limit","workflow"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}