{"record":{"id":"edf382558f8122ed","repo":"github/spec-kit","slug":"invalid-command-name-cmd-name-r-name-reason","errorCode":null,"errorMessage":"Invalid command name {cmd_name!r}: {name_reason}","messagePattern":"Invalid command name (.+?): (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/agents.py","lineNumber":696,"sourceCode":"        _sep = agent_config.get(\"invoke_separator\", \".\")\n        registrar_writes_skills = agent_config.get(\"extension\") == \"/SKILL.md\"\n        try:\n            from specify_cli.integrations import get_integration  # noqa: PLC0415\n\n            _integ = get_integration(agent_name)\n            if _integ is not None:\n                _sep = _integ.invoke_separator_for_mode(registrar_writes_skills)\n        except (ImportError, ValueError, KeyError):\n            pass\n        _prefix = get_invocation_prefix(agent_name, registrar_writes_skills)\n\n        for cmd_info in commands:\n            cmd_name = cmd_info[\"name\"]\n            aliases = cmd_info.get(\"aliases\", [])\n            cmd_file = cmd_info[\"file\"]\n            name_reason = relative_extension_path_violation(cmd_name)\n            if name_reason:\n                raise ValueError(\n                    f\"Invalid command name {cmd_name!r}: {name_reason}\"\n                )\n            if aliases is None:\n                aliases = []\n            if not isinstance(aliases, list):\n                raise ValueError(\n                    f\"Aliases for command {cmd_name!r} must be a list\"\n                )\n            for alias in aliases:\n                alias_reason = relative_extension_path_violation(alias)\n                if alias_reason:\n                    raise ValueError(\n                        f\"Invalid command alias {alias!r}: {alias_reason}\"\n                    )\n\n            # Guard against path traversal using the single shared policy in\n            # relative_extension_path_violation(), so the runtime guard stays\n            # aligned with ExtensionManifest._validate() and the skill/preset","sourceCodeStart":678,"sourceCodeEnd":714,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/agents.py#L678-L714","documentation":"Before writing any command file, the registrar runs each command name through relative_extension_path_violation(), the shared policy that rejects absolute paths, anchored paths, empty/whitespace names, and '..' traversal. A violating name raises ValueError naming the command and the reason, keeping the runtime guard aligned with ExtensionManifest validation and the skill/preset readers.","triggerScenarios":"A commands list entry with name 'sub/../../cmd', '/etc/cmd', 'cmd/', '' or '   '; names assembled from extension data where user input contains slashes or dot-dot segments.","commonSituations":"Extension manifests with names containing path separators expecting subdirectories; template-generated names that embed a path; malicious extension content attempting to escape the commands directory. Note: file fields with violations are skipped ('continue'), but name violations are hard errors.","solutions":["Use a flat, relative name: lowercase identifier with no '/', '\\', leading '..', or absolute prefix.","If subdirectories are needed, check whether the target agent supports them at all before encoding paths in the name.","Validate extension-provided names with the same helper (from specify_cli import relative_extension_path_violation) before calling register."],"exampleFix":"# before\ncommands = [{\"name\": \"../escape\", \"file\": \"plan.md\"}]\n# after\ncommands = [{\"name\": \"my-extension-plan\", \"file\": \"plan.md\"}]","handlingStrategy":"validation","validationCode":"from specify_cli import relative_extension_path_violation\n\nbad = [c[\"name\"] for c in commands if relative_extension_path_violation(c[\"name\"])]\nif bad:\n    raise SystemExit(f\"unsafe command names: {bad}\")","typeGuard":"from specify_cli import relative_extension_path_violation\n\ndef is_safe_command_name(name: str) -> bool:\n    \"\"\"True when the shared policy finds no path violation in name.\"\"\"\n    return relative_extension_path_violation(name) is None","tryCatchPattern":"try:\n    registrar.register_commands(...)\nexcept ValueError as exc:\n    if \"Invalid command name\" in str(exc):\n        raise SystemExit(f\"rejected unsafe command name: {exc}\") from exc\n    raise","preventionTips":["Restrict command names to flat lowercase identifiers (letters, digits, '-', '_', '.').","Run relative_extension_path_violation over extension-provided names in CI.","Treat this error as a security signal — audit where the name came from, don't just rename."],"tags":["security","path-traversal","commands","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}