From ff437b11fd6f406b764b69fd176c1e2904b933ef Mon Sep 17 00:00:00 2001 From: jawwad-ali Date: Fri, 31 Jul 2026 12:44:51 +0500 Subject: [PATCH] fix(workflows): keep the init step's documented ignore_agent_tools default on an explicit null The step documents the default twice: class docstring: "Because workflows run unattended, the step defaults to ``--ignore-agent-tools``" field docs: "Skip checks for the coding agent CLI (defaults to ``true``)" It implements that with `config.get("ignore_agent_tools", True)`, which applies the default only when the key is ABSENT. A bare `ignore_agent_tools:` in YAML parses to None, and `_resolve_bool(None)` returns False: key ABSENT -> True flag emitted: YES bare ignore_agent_tools: -> False flag emitted: NO <-- bug explicit true -> True flag emitted: YES explicit false -> False flag emitted: NO So the flag is dropped, `specify init` re-runs the agent-CLI presence check, and an unattended run fails with "Agent Detection Error" for any integration whose CLI is not installed on the runner. Normalize an explicit null to the default, mirroring the while/do-while `max_iterations` handling. Co-Authored-By: Claude Opus 5 (1M context) --- .../workflows/steps/init/__init__.py | 14 +++-- tests/test_workflows.py | 53 +++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) 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