diff --git a/src/specify_cli/workflows/steps/init/__init__.py b/src/specify_cli/workflows/steps/init/__init__.py index 5dc1ee9c02..e72af67338 100644 --- a/src/specify_cli/workflows/steps/init/__init__.py +++ b/src/specify_cli/workflows/steps/init/__init__.py @@ -91,9 +91,17 @@ def execute(self, config: dict[str, Any], context: StepContext) -> StepResult: force = self._resolve_bool(config.get("force"), context) # Workflows run unattended; skip the agent CLI presence check by default. - ignore_agent_tools = self._resolve_bool( - config.get("ignore_agent_tools", True), context - ) + # ``config.get(key, True)`` applies that default only when the key is + # ABSENT: a bare ``ignore_agent_tools:`` in YAML parses to None, which + # ``_resolve_bool`` then turns into False -- flipping the documented + # default and re-enabling the agent-CLI presence check this step promises + # to skip, so an unattended run fails with "Agent Detection Error" for + # any integration whose CLI is not installed. Normalize an explicit null + # to the default, mirroring the while/do-while ``max_iterations`` handling. + raw_ignore_agent_tools = config.get("ignore_agent_tools") + if raw_ignore_agent_tools is None: + raw_ignore_agent_tools = True + ignore_agent_tools = self._resolve_bool(raw_ignore_agent_tools, context) argv: list[str] = ["init"] if here: diff --git a/tests/test_workflows.py b/tests/test_workflows.py index 57536081ea..d63a7e00be 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -2173,6 +2173,59 @@ def test_builds_here_argv_and_bootstraps(self, tmp_path): assert "--ignore-agent-tools" in argv assert (tmp_path / ".specify").is_dir() + def test_explicit_null_ignore_agent_tools_keeps_documented_default( + self, tmp_path + ): + """A bare ``ignore_agent_tools:`` must keep the documented default. + + The class docstring says "Because workflows run unattended, the step + defaults to ``--ignore-agent-tools``" and the field docs say "defaults to + ``true``". But ``config.get(key, True)`` applies the default only when the + key is ABSENT — a bare ``ignore_agent_tools:`` in YAML parses to None, + which ``_resolve_bool`` turned into False, dropping the flag and + re-enabling the agent-CLI presence check for an unattended run. + """ + from specify_cli.workflows.steps.init import InitStep + from specify_cli.workflows.base import StepContext, StepStatus + + step = InitStep() + ctx = StepContext( + project_root=str(tmp_path), default_integration="copilot" + ) + result = step.execute( + { + "id": "bootstrap", + "here": True, + "script": "sh", + "ignore_agent_tools": None, + }, + ctx, + ) + + assert result.status == StepStatus.COMPLETED + assert "--ignore-agent-tools" in result.output["argv"] + + def test_explicit_false_ignore_agent_tools_is_honoured(self, tmp_path): + """An explicit ``false`` must still opt in to the agent-CLI check.""" + from specify_cli.workflows.steps.init import InitStep + from specify_cli.workflows.base import StepContext + + step = InitStep() + ctx = StepContext( + project_root=str(tmp_path), default_integration="copilot" + ) + result = step.execute( + { + "id": "bootstrap", + "here": True, + "script": "sh", + "ignore_agent_tools": False, + }, + ctx, + ) + + assert "--ignore-agent-tools" not in result.output["argv"] + def test_default_integration_falls_back_to_workflow_default(self, tmp_path): from specify_cli.workflows.steps.init import InitStep from specify_cli.workflows.base import StepContext, StepStatus