fix(workflows): keep the init step's documented ignore_agent_tools default on an explicit null (#3889)

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) <noreply@anthropic.com>
This commit is contained in:
Ali jawwad
2026-08-03 13:14:31 -05:00
committed by GitHub
co-authored by Claude Opus 5
parent b69147c841
commit 84a2114338
2 changed files with 64 additions and 3 deletions
@@ -95,9 +95,17 @@ class InitStep(StepBase):
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:
+53
View File
@@ -2176,6 +2176,59 @@ class TestInitStep:
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