Skip to content

feat(integrations): support SPECIFY_<KEY>_EXTRA_ARGS env var for agent subprocess flags#2596

Merged
mnriem merged 9 commits into
github:mainfrom
doquanghuy:feat/integrations-extra-args
May 28, 2026
Merged

feat(integrations): support SPECIFY_<KEY>_EXTRA_ARGS env var for agent subprocess flags#2596
mnriem merged 9 commits into
github:mainfrom
doquanghuy:feat/integrations-extra-args

Conversation

@doquanghuy
Copy link
Copy Markdown
Contributor

@doquanghuy doquanghuy commented May 16, 2026

Description

Closes #2595.

Adds a per-integration env-var hook
(SPECKIT_INTEGRATION_<KEY>_EXTRA_ARGS) so operators can inject
extra CLI flags into the spawned agent subprocess without modifying
any SKILL or workflow YAML. The motivating use case is
non-interactive contexts (CI, batch, sandboxed eval) where the
spawned <agent> -p ... hangs silently on an internal permission
or input prompt invisible to the parent specify workflow run
process.

The INTEGRATION segment scopes the variable to this subsystem so
it does not collide with other Spec Kit env-var namespaces. The
SPECKIT_ prefix matches the existing integration-subsystem env
vars (SPECKIT_INTEGRATION_CATALOG_URL,
SPECKIT_COPILOT_ALLOW_ALL_TOOLS), keeping the namespace
consistent. (Original draft and linked issue #2595 used the
SPECIFY_* prefix; the implementation landed on SPECKIT_* for
namespace alignment.)

What changed

  • New helper IntegrationBase._apply_extra_args_env_var(args)
    reads SPECKIT_INTEGRATION_<KEY>_EXTRA_ARGS from env (key
    normalized: -_, uppercased), parses with shlex.split,
    and appends to args. No-op when the env var is unset or
    whitespace-only.
  • MarkdownIntegration.build_exec_args,
    TomlIntegration.build_exec_args, and
    SkillsIntegration.build_exec_args each call the helper between
    args = [self.key, "-p", prompt] and the model / output-format
    appends — so extra args sit between -p prompt and the
    canonical Spec Kit flags and cannot clobber them.
  • The four integrations that override build_exec_args
    (CodexIntegration, DevinIntegration, OpencodeIntegration,
    CopilotIntegration) also call the helper in the matching
    position so the env var works for every requires_cli
    integration.
  • CopilotIntegration.dispatch_command builds cli_args inline
    rather than going through build_exec_args; the hook is invoked
    there too so workflow execution honours the env var (not just
    build_exec_args callers).
  • New tests/integrations/test_extra_args.py with 17 tests
    covering: default no-op, single-token, multi-token via shlex,
    whitespace-only treated as unset, malformed-quoting actionable
    error, other-integration env var ignored (per-key scoping),
    key normalization (kiro-cliKIRO_CLI), requires_cli: False
    short-circuit, per-integration build_exec_args checks for
    Codex/Devin/Opencode/Copilot plus base-class coverage for
    MarkdownIntegration and TomlIntegration, Opencode precedence
    vs. prompt-derived --command, and end-to-end dispatch_command
    checks (Copilot
    override path + inherited base path).

Default behaviour preserved

When SPECKIT_INTEGRATION_<KEY>_EXTRA_ARGS is unset for the active
integration, build_exec_args returns byte-identical argv to
before this change. Existing operators see no difference.

Examples

# Non-interactive Claude run in CI
SPECKIT_INTEGRATION_CLAUDE_EXTRA_ARGS="--dangerously-skip-permissions" \
    specify workflow run my-pipeline

# Per-integration scoping
SPECKIT_INTEGRATION_GEMINI_EXTRA_ARGS="--max-turns 3" specify workflow run …

# Multi-token via shlex
SPECKIT_INTEGRATION_CLAUDE_EXTRA_ARGS="--dangerously-skip-permissions --max-turns 3"# Hyphenated keys
SPECKIT_INTEGRATION_KIRO_CLI_EXTRA_ARGS="--some-flag" specify workflow run …

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests: pytest tests/ -q
    3011 passed, 40 skipped (was 2997 on the pre-PR base;
    +17 new tests added in this PR — full suite green locally
    and across the CI matrix on Ubuntu 3.11/3.12/3.13 +
    Windows 3.11/3.12/3.13).
  • Tested with a sample project: invoked specify workflow run
    against a Claude command step with
    SPECKIT_INTEGRATION_CLAUDE_EXTRA_ARGS=--dangerously-skip-permissions
    set — flag reaches the spawned claude -p ... subprocess
    and the run completes non-interactively. Same workflow
    without the env var still hangs as before (consistent with
    the pre-PR behaviour).

Manual test results

Agent: Claude Code (CLI) | OS/Shell: macOS 14 / zsh

Test Result Notes
Env var set → flag in argv PASS --dangerously-skip-permissions appears between -p prompt and --model
Env var unset → byte-identical argv PASS argv matches pre-PR shape exactly
Other-integration env var ignored PASS SPECKIT_INTEGRATION_GEMINI_EXTRA_ARGS=... does not affect Claude argv
Multi-token via shlex PASS "--flag-a --flag-b 3" splits into 3 tokens
Whitespace-only treated as unset PASS " " → no-op
Key normalization kiro-cliKIRO_CLI PASS env var SPECKIT_INTEGRATION_KIRO_CLI_EXTRA_ARGS correctly read
requires_cli: False short-circuit PASS build_exec_args returns None; env-var hook never reached

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (described below)

Used Claude Opus to draft the implementation (env-var hook +
helper extraction), the test suite, the issue body for #2595, and
this PR body. The reproducer scenario (non-interactive claude -p
hang on permission prompts) was a real-world problem encountered
in operator practice. Code, tests, and design decisions were
human-reviewed before submission.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a generic env-var hook SPECIFY_<KEY>_EXTRA_ARGS so operators can append CLI flags to the spawned agent subprocess (e.g. --dangerously-skip-permissions for Claude in CI/non-interactive contexts) without editing skills, workflows, or integration code. The hook is implemented once on IntegrationBase and invoked from the three concrete build_exec_args implementations in base.py.

Changes:

  • New IntegrationBase._apply_extra_args_env_var helper that reads SPECIFY_<KEY>_EXTRA_ARGS, normalizes the key (hyphens→underscores, uppercased), and shlex.splits the value into args.
  • MarkdownIntegration, TomlIntegration, and SkillsIntegration build_exec_args now call the helper between [key, -p, prompt] and the --model / --output-format flags.
  • Adds tests/integrations/test_extra_args.py covering no-op default, single/multi-token parsing, whitespace handling, per-integration scoping, key normalization, and requires_cli=False short-circuit.
Show a summary per file
File Description
src/specify_cli/integrations/base.py Adds _apply_extra_args_env_var helper and calls it from the three concrete build_exec_args implementations.
tests/integrations/test_extra_args.py New test module exercising the env-var hook through stub SkillsIntegration subclasses.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread src/specify_cli/integrations/base.py Outdated
Comment thread tests/integrations/test_extra_args.py Outdated
doquanghuy added a commit to doquanghuy/spec-kit that referenced this pull request May 18, 2026
…ncode/Copilot

Four integrations override `build_exec_args` and were silently
ignoring the env-var hook introduced in the previous commit:

- CodexIntegration (`codex exec ...`)
- DevinIntegration (`devin -p ...`)
- OpencodeIntegration (`opencode run ...`)
- CopilotIntegration (`copilot -p ...`)

Each now calls `self._apply_extra_args_env_var(args)` between the
base argv and the canonical Spec Kit flags (matching the placement
in `MarkdownIntegration`, `TomlIntegration`, and `SkillsIntegration`),
so operator-injected flags cannot clobber `--model` / `--output-format`
/ `--json`.

Adds 4 parameterized override-integration tests locking the wiring
per agent. Also cleans up an inline `__import__("os").environ` in the
fixture to a top-of-file `import os`.

Drive-by typing fix: guard `self.registrar_config.get(...)` in
`CopilotIntegration.add_commands` against the `None` case, matching
the pattern already used in `base.py` for the same access.

Addresses Copilot review on github#2596.
@doquanghuy
Copy link
Copy Markdown
Contributor Author

Pushed 8341c12 addressing both Copilot findings:

1. Override integrations wired up. CodexIntegration, DevinIntegration, OpencodeIntegration, and CopilotIntegration each override build_exec_args — they now call self._apply_extra_args_env_var(args) between the base argv and the canonical Spec Kit flags. So SPECIFY_CODEX_EXTRA_ARGS, SPECIFY_DEVIN_EXTRA_ARGS, SPECIFY_OPENCODE_EXTRA_ARGS, and SPECIFY_COPILOT_EXTRA_ARGS all reach their respective subprocesses, matching the documented "every requires_cli integration" contract.

2. Test fixture cleanup. Replaced inline __import__("os").environ with a top-of-file import os.

Coverage. Added 4 parameterized override-integration tests locking the wiring per agent — full suite is now 2947 passed (was 2943), 35 skipped, no regressions.

Drive-by. Pyright surfaced a pre-existing latent Optional[dict].get(...) access at copilot/__init__.py:362. Fixed it inline using the same guard pattern already used in base.py for the same access — no behaviour change in practice (every concrete integration sets registrar_config), but the type check now passes cleanly.

AI disclosure: drafted with Claude Opus, human-reviewed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Comments suppressed due to low confidence (1)

src/specify_cli/integrations/copilot/init.py:149

  • CopilotIntegration overrides dispatch_command() and constructs its own cli_args without calling _apply_extra_args_env_var. Since workflow command steps call impl.dispatch_command(...), SPECIFY_COPILOT_EXTRA_ARGS will not actually reach the spawned copilot subprocess in workflows even though build_exec_args() now supports it. Apply the env-var hook in dispatch_command() as well (ideally right after cli_args = ["copilot", "-p", prompt] and before adding --agent/--yolo/--model/--output-format so canonical flags still win).
        args = ["copilot", "-p", prompt]
        self._apply_extra_args_env_var(args)
        if _allow_all():
            args.append("--yolo")
        if model:
            args.extend(["--model", model])
        if output_json:
            args.extend(["--output-format", "json"])
        return args
  • Files reviewed: 6/6 changed files
  • Comments generated: 2

Comment thread src/specify_cli/integrations/opencode/__init__.py
Comment thread tests/integrations/test_extra_args.py Outdated
doquanghuy added a commit to doquanghuy/spec-kit that referenced this pull request May 21, 2026
…command

When the Opencode prompt starts with `/`, `build_exec_args` injects
`--command <X>` derived from the prompt. The previous placement of
`self._apply_extra_args_env_var(args)` appended operator-injected
args AFTER that `--command`, so a user setting
`SPECIFY_OPENCODE_EXTRA_ARGS="--command override"` could redirect the
command under typical last-wins repeated-flag CLI semantics.

Move the hook to immediately after `args = [self.key, "run"]`, before
the prompt-parsing block. The operator's `--command override` (if
any) now precedes the Spec Kit-derived `--command speckit`, so the
canonical choice wins.

Adds `test_opencode_extra_args_cannot_clobber_prompt_derived_command`
locking the ordering. Also corrects the module docstring to describe
the hook as living in `IntegrationBase` (not `SkillsIntegration`) and
to acknowledge that this file covers Codex/Devin/Opencode/Copilot in
addition to SkillsIntegration stubs.

Addresses Copilot review on github#2596.
@doquanghuy
Copy link
Copy Markdown
Contributor Author

Pushed a17b951 addressing both new Copilot findings:

1. Opencode ordering fix. Moved self._apply_extra_args_env_var(args) to immediately after args = [self.key, "run"], before the prompt-parsing block. Operator-injected args now precede the prompt-derived --command <X>, so under typical last-wins repeated-flag CLI semantics Spec Kit's command selection remains authoritative even if SPECIFY_OPENCODE_EXTRA_ARGS contains a stray --command. Added test_opencode_extra_args_cannot_clobber_prompt_derived_command to lock the ordering.

2. Test docstring. Rewrote the module docstring to describe the hook as living in IntegrationBase._apply_extra_args_env_var (not SkillsIntegration) and to acknowledge the file now covers Codex/Devin/Opencode/Copilot in addition to the SkillsIntegration stubs near the top.

Full suite: 2948 passed (+1), 35 skipped, no regressions.

AI disclosure: drafted with Claude Opus, human-reviewed.

Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use SPECIFY_INTEGRATION__EXTRA_ARGS so we know it is targeting the integration subsystem. Good addition overall!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 2

Comment thread src/specify_cli/integrations/copilot/__init__.py Outdated
Comment thread tests/integrations/test_extra_args.py
doquanghuy added a commit to doquanghuy/spec-kit that referenced this pull request May 21, 2026
`CopilotIntegration` is the only integration that overrides
`dispatch_command` — it builds `cli_args` inline rather than going
through `build_exec_args`. The previous commit wired
`_apply_extra_args_env_var` into `build_exec_args` but workflow
execution calls `dispatch_command`, so `SPECIFY_COPILOT_EXTRA_ARGS`
was silently ignored at runtime.

Add the hook in `dispatch_command` immediately after
`cli_args = ["copilot", "-p", prompt]`, mirroring the placement in
`build_exec_args` (between `-p prompt` and the canonical
`--agent` / `--yolo` / `--model` / `--output-format` flags).

`IntegrationBase.dispatch_command` already delegates to
`build_exec_args`, so Codex, Devin, and Opencode continue to honour
their respective env vars through inheritance — no further changes
needed for them.

Adds two end-to-end tests that monkeypatch `subprocess.run` and
assert the env-var args reach the executed argv:
- `test_copilot_dispatch_command_includes_extra_args` locks the
  bypass fix on the overridden path.
- `test_codex_dispatch_command_includes_extra_args` locks the
  inherited-base-dispatch path for the other three integrations.

Addresses Copilot review on github#2596.
@doquanghuy
Copy link
Copy Markdown
Contributor Author

Pushed acab474 addressing both new findings:

1. Copilot dispatch_command bypass (the real bug). CopilotIntegration is the only integration that overrides dispatch_command — it builds cli_args inline rather than going through build_exec_args. The hook is now invoked in dispatch_command immediately after cli_args = ["copilot", "-p", prompt], between -p prompt and the canonical --agent / --yolo / --model / --output-format flags. Audit confirmed Copilot is the only override — Codex, Devin, Opencode all inherit IntegrationBase.dispatch_command which already delegates to build_exec_args, so they continue to honour their env vars through inheritance.

2. End-to-end test coverage. Added two tests that monkeypatch subprocess.run and assert the env-var args reach the executed argv:

  • test_copilot_dispatch_command_includes_extra_args locks the bypass fix on the overridden path.
  • test_codex_dispatch_command_includes_extra_args locks the inherited-base-dispatch path for Codex/Devin/Opencode by transitivity.

Full suite: 2950 passed (was 2948), 35 skipped, no regressions.

AI disclosure: drafted with Claude Opus, human-reviewed.

doquanghuy added a commit to doquanghuy/spec-kit that referenced this pull request May 21, 2026
…XTRA_ARGS

Per maintainer request: scope the operator-injected env var to the
integration subsystem by prepending `INTEGRATION_` to the key
segment, so it does not collide with other Spec Kit env-var
namespaces.

Renames everywhere it appears:
- Helper `IntegrationBase._apply_extra_args_env_var` env_name
  format and docstring (`base.py`).
- Inline comment in `CopilotIntegration.dispatch_command`.
- All `monkeypatch.setenv(...)` calls, docstrings, and the
  autouse fixture's scope filter in
  `tests/integrations/test_extra_args.py`.

No behaviour change beyond the variable name. Default (env var
unset) still byte-identical to before this PR.

Addresses review on github#2596.
@doquanghuy
Copy link
Copy Markdown
Contributor Author

@mnriem — done. Pushed 932cfb9 renaming the env var to SPECIFY_INTEGRATION_<KEY>_EXTRA_ARGS so the namespace is unambiguously scoped to the integration subsystem.

Updated everywhere it lives:

  • Helper IntegrationBase._apply_extra_args_env_var env-name format and docstring.
  • Inline comment in CopilotIntegration.dispatch_command.
  • All monkeypatch.setenv(...) calls, docstrings, and the autouse fixture's scope filter in tests/integrations/test_extra_args.py.
  • PR description (examples + reference table).

No behaviour change beyond the variable name. Full suite still 2950 passed, 35 skipped.

AI disclosure: drafted with Claude Opus, human-reviewed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 1

Comment thread src/specify_cli/integrations/base.py Outdated
doquanghuy added a commit to doquanghuy/spec-kit that referenced this pull request May 27, 2026
…ting

Wrap `shlex.split` in `_apply_extra_args_env_var` so an unmatched quote
in `SPECIFY_INTEGRATION_<KEY>_EXTRA_ARGS` surfaces a clear `ValueError`
naming the offending env var and showing the invalid value, instead of
crashing workflow dispatch with a bare shlex traceback. Adds a new test
locking the actionable error path.

Addresses Copilot review feedback on github#2596.
@doquanghuy
Copy link
Copy Markdown
Contributor Author

doquanghuy commented May 27, 2026

@mnriem — addressed the latest Copilot finding in 0ddac52, and rebased the branch onto current main (faf5129) to clear the conflict against _copilot_executable() that landed upstream.

The fix. IntegrationBase._apply_extra_args_env_var now wraps shlex.split in a try/except ValueError and re-raises a ValueError that:

  1. Names the offending env var (SPECIFY_INTEGRATION_<KEY>_EXTRA_ARGS).
  2. Echoes the invalid value with repr so quoting issues are visible.
  3. Shows a concrete fix example ('--flag "value with spaces"').
  4. Chains the original shlex exception via from exc so the underlying message is preserved.

A malformed env var now surfaces as a clear operator-facing error instead of a bare shlex traceback through workflow dispatch.

The rebase. Three of the existing commits touched the same line in src/specify_cli/integrations/copilot/__init__.py that #2640 (or whichever PR introduced _copilot_executable()) rewrote. Resolved by keeping _copilot_executable() everywhere and re-applying self._apply_extra_args_env_var(...) on the rewritten lines. No semantic change vs. the pre-rebase branch — same env-var hook, same placement between -p prompt and the canonical flags.

Test coverage. New test_malformed_quoting_raises_actionable_value_error locks the actionable-error path — feeds --flag "unterminated and asserts the message contains both the env-var name and the offending value. Full suite is now 3009 passed, 40 skipped (was 2950 / 35 pre-rebase — picked up upstream-main tests too), no regressions.

Branch is now MERGEABLE again and ready for another look whenever you have a moment.

AI disclosure: drafted with Claude Opus, human-reviewed.

doquanghuy and others added 3 commits May 27, 2026 19:53
…t subprocess flags

Read a per-integration env var (SPECIFY_<KEY>_EXTRA_ARGS) inside
`SkillsIntegration.build_exec_args`, `MarkdownIntegration.build_exec_args`,
and `TomlIntegration.build_exec_args` and append the parsed flags to the
spawned agent's argv, gated per integration key.

Operators can now opt into extra CLI flags (e.g.
`SPECIFY_CLAUDE_EXTRA_ARGS=--dangerously-skip-permissions`) without
modifying any SKILL or workflow YAML. Useful in CI / non-interactive
contexts where the spawned `<agent> -p ...` would otherwise hang on
an internal permission or input prompt invisible to the parent
`specify workflow run` process.

Key normalization: `kiro-cli` → `SPECIFY_KIRO_CLI_EXTRA_ARGS` (hyphen
replaced with underscore, then uppercased).

Default (env var unset or whitespace-only) is byte-identical to
previous behaviour. Extra args are inserted between `-p prompt` and
the model / output-format flags so they cannot clobber canonical
Spec Kit args.

Implementation: a single helper `IntegrationBase._apply_extra_args_env_var`
encapsulates the env-var read + shlex parsing; each of the three
concrete `build_exec_args` implementations calls it.

Closes github#2595

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ncode/Copilot

Four integrations override `build_exec_args` and were silently
ignoring the env-var hook introduced in the previous commit:

- CodexIntegration (`codex exec ...`)
- DevinIntegration (`devin -p ...`)
- OpencodeIntegration (`opencode run ...`)
- CopilotIntegration (`copilot -p ...`)

Each now calls `self._apply_extra_args_env_var(args)` between the
base argv and the canonical Spec Kit flags (matching the placement
in `MarkdownIntegration`, `TomlIntegration`, and `SkillsIntegration`),
so operator-injected flags cannot clobber `--model` / `--output-format`
/ `--json`.

Adds 4 parameterized override-integration tests locking the wiring
per agent. Also cleans up an inline `__import__("os").environ` in the
fixture to a top-of-file `import os`.

Drive-by typing fix: guard `self.registrar_config.get(...)` in
`CopilotIntegration.add_commands` against the `None` case, matching
the pattern already used in `base.py` for the same access.

Addresses Copilot review on github#2596.
…command

When the Opencode prompt starts with `/`, `build_exec_args` injects
`--command <X>` derived from the prompt. The previous placement of
`self._apply_extra_args_env_var(args)` appended operator-injected
args AFTER that `--command`, so a user setting
`SPECIFY_OPENCODE_EXTRA_ARGS="--command override"` could redirect the
command under typical last-wins repeated-flag CLI semantics.

Move the hook to immediately after `args = [self.key, "run"]`, before
the prompt-parsing block. The operator's `--command override` (if
any) now precedes the Spec Kit-derived `--command speckit`, so the
canonical choice wins.

Adds `test_opencode_extra_args_cannot_clobber_prompt_derived_command`
locking the ordering. Also corrects the module docstring to describe
the hook as living in `IntegrationBase` (not `SkillsIntegration`) and
to acknowledge that this file covers Codex/Devin/Opencode/Copilot in
addition to SkillsIntegration stubs.

Addresses Copilot review on github#2596.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new

@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 27, 2026

Please address test errors

… test

`test_copilot_integration_honours_extra_args` hardcoded `"copilot"`
in the expected argv, but `CopilotIntegration.build_exec_args` calls
`_copilot_executable()` which returns `"copilot.cmd"` on Windows
(`os.name == "nt"`). The test passed on Linux/macOS and failed on
all three Windows-latest matrix entries.

Resolve by importing `_copilot_executable` alongside `CopilotIntegration`
and using it as the first expected argv token. The companion
`test_copilot_dispatch_command_includes_extra_args` already uses
`index()` lookups rather than full-argv equality so it was unaffected.
@doquanghuy
Copy link
Copy Markdown
Contributor Author

@mnriem — Windows pytest failure fixed in e66a090.

Root cause. test_copilot_integration_honours_extra_args hardcoded "copilot" as the first expected argv token, but CopilotIntegration.build_exec_args calls _copilot_executable() which returns "copilot.cmd" on Windows (os.name == "nt"). The test passed on all three Ubuntu matrix entries and failed on all three Windows-latest entries (3.11 / 3.12 / 3.13) with:

E   AssertionError: assert ['copilot.cmd', '-p', 'p', '--allow-tool', ...]
                       == ['copilot',     '-p', 'p', '--allow-tool', ...]

Fix. Import _copilot_executable alongside CopilotIntegration and use it as the first expected argv token, matching the platform-aware pattern already used elsewhere in tests/test_workflows.py ("copilot.cmd" if os.name == "nt" else "copilot"). The companion test_copilot_dispatch_command_includes_extra_args was already platform-safe — it uses argv.index(...) lookups rather than full-argv equality.

Audit for similar issues on the sibling PRs. Checked #2663 and #2664 for the same Windows hazard pattern:

Full local suite: 3009 passed, 40 skipped. Re-pushed to origin/feat/integrations-extra-args.

AI disclosure: drafted with Claude Opus, human-reviewed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 2

Comment thread src/specify_cli/integrations/codex/__init__.py Outdated
Comment thread tests/integrations/test_extra_args.py
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 27, 2026

Please address Copilot feedback

…lasses

Two Copilot findings on the latest pass:

1. `CodexIntegration.build_exec_args` hardcoded the executable name
   as the literal `"codex"` while the env-var lookup derives from
   `self.key`. The two should stay coupled (matching Devin/Opencode,
   which both use `self.key` already). Replace the literal with
   `self.key` so the argv and env-var scoping cannot drift.

2. `tests/integrations/test_extra_args.py` covered the
   `SkillsIntegration` mechanism (via stubs near the top) and the
   four `build_exec_args` overrides (Codex/Devin/Opencode/Copilot)
   end-to-end, but did not exercise the `MarkdownIntegration` or
   `TomlIntegration` base implementations directly. Add bare
   `_MarkdownAgentStub` and `_TomlAgentStub` test stubs and a test
   each — the most common integration pattern (Amp, Auggie, Generic,
   Gemini, Tabnine, …) inherits without overriding, so the base
   wiring is now locked.

Full suite: 3011 passed (was 3009), 40 skipped, no regressions.

Addresses Copilot review feedback on github#2596.
@doquanghuy
Copy link
Copy Markdown
Contributor Author

@mnriem — addressed both new Copilot findings in 1ae8d52.

Why does Copilot keep finding things? Honest answer: each successive pass surfaces something the previous pass missed, not a deeper code-quality issue. The PR is incrementally tightening as the visible surface area expands (override integrations → dispatch_command bypass → malformed-quoting error → Windows portability → now executable-name coupling + base-class coverage). The underlying mechanism — single helper on IntegrationBase, applied between -p prompt and the canonical flags — has not changed since the original commit.

1. Codex executable name decoupled from self.key. CodexIntegration.build_exec_args hardcoded the executable name as the literal "codex" while the env-var lookup derived from self.key. The two should stay coupled — if self.key ever changes (rename, subclass), the argv would drift from the env-var scoping and silently break the env-var contract. DevinIntegration and OpencodeIntegration already use self.key here. Replaced the literal in Codex to match. Inline comment explains why so a future reader doesn't "simplify" it back to a literal.

2. Base-class coverage added. The test suite covered the SkillsIntegration mechanism (via stubs at the top) and all four build_exec_args overrides (Codex/Devin/Opencode/Copilot) end-to-end, but did not exercise MarkdownIntegration.build_exec_args or TomlIntegration.build_exec_args directly. Since most concrete integrations inherit those without overriding (Amp, Auggie, Generic, Bob, Forge, Iflow, Junie, Kilocode, KiroCli, Pi, Qodercli, Qwen, Roo, Shai, Windsurf → MarkdownIntegration; Gemini, Tabnine → TomlIntegration), the base wiring was the largest uncovered surface. Added two bare stubs (_MarkdownAgentStub, _TomlAgentStub) that subclass the bases without overriding, and two tests:

  • test_markdown_integration_base_honours_extra_args — locks the base MarkdownIntegration.build_exec_args hook position (between -p prompt and --model / --output-format).
  • test_toml_integration_base_honours_extra_args — locks the base TomlIntegration.build_exec_args hook position, including the TOML-specific -m (vs Markdown's --model) detail.

Full suite: 3011 passed (was 3009), 40 skipped, no regressions. Pushed to origin/feat/integrations-extra-args.

AI disclosure: drafted with Claude Opus, human-reviewed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 2

Comment thread src/specify_cli/integrations/base.py
Comment thread src/specify_cli/integrations/copilot/__init__.py Outdated
…ARGS

Renames the env-var hook prefix from `SPECIFY_INTEGRATION_*` to
`SPECKIT_INTEGRATION_*` to match the established codebase
convention for integration-subsystem env vars
(`SPECKIT_INTEGRATION_CATALOG_URL` in `integrations/catalog.py`,
`SPECKIT_COPILOT_ALLOW_ALL_TOOLS` in `integrations/copilot/__init__.py`).

The `SPECIFY_*` prefix is reserved for user-facing
feature-resolution variables (`SPECIFY_FEATURE`,
`SPECIFY_FEATURE_DIRECTORY`); reusing it for integration-subsystem
scoping would introduce a second integration namespace under a
different prefix, confusing operators who already set
`SPECKIT_INTEGRATION_CATALOG_URL`.

Also reverts the unrelated defensive `arg_placeholder` /
`registrar_config is None` guard in
`CopilotIntegration.setup_skills_mode` — it was a drive-by pyright
cleanup mixed into this PR. Every concrete integration sets
`registrar_config` so the guard never fires in practice; the
typing issue belongs in a focused follow-up rather than this
env-var-hook PR.

Updates everywhere the old prefix appeared:
- `IntegrationBase._apply_extra_args_env_var` helper + docstring
- `CopilotIntegration.dispatch_command` inline comment
- All `monkeypatch.setenv(...)` calls in `tests/integrations/test_extra_args.py`
- The autouse fixture scope filter
- Test module docstring

Full suite: 3011 passed, 40 skipped, no regressions.

Addresses Copilot review feedback on github#2596.
@doquanghuy
Copy link
Copy Markdown
Contributor Author

@mnriem — addressed both new Copilot findings in 101f35b.

1. Env-var prefix renamed SPECIFY_INTEGRATION_*SPECKIT_INTEGRATION_*. Copilot caught a real namespace inconsistency: the codebase already uses SPECKIT_* for integration-subsystem env vars (SPECKIT_INTEGRATION_CATALOG_URL, SPECKIT_COPILOT_ALLOW_ALL_TOOLS), while SPECIFY_* is reserved for user-facing feature-resolution (SPECIFY_FEATURE, SPECIFY_FEATURE_DIRECTORY). Introducing a second integration namespace under a different prefix would have confused operators. Renamed everywhere it appeared: helper + docstring, inline comment in CopilotIntegration.dispatch_command, every monkeypatch.setenv(...) call, the autouse fixture scope filter, and the test module docstring. The earlier rename (SPECIFY_<KEY>_EXTRA_ARGSSPECIFY_INTEGRATION_<KEY>_EXTRA_ARGS) was the right idea but picked the wrong prefix; this lands the correctly-scoped one.

2. Reverted the unrelated arg_placeholder defensive guard. Copilot was right — the registrar_config is None guard in CopilotIntegration.setup_skills_mode (line 377) was a drive-by pyright cleanup I added in 8341c12 that's unrelated to the env-var-hook contract. Every concrete integration sets registrar_config, so the guard never fires in practice. Reverted to the original single-line arg_placeholder = self.registrar_config.get("args", "$ARGUMENTS"). If pyright complains in CI about the Optional[dict].get(...) access, that's a focused follow-up — not this PR's job.

Both updated everywhere consistently. Full suite still passes: 3011 passed, 40 skipped, no regressions.

AI disclosure: drafted with Claude Opus, human-reviewed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 1

Comment thread src/specify_cli/integrations/base.py
@doquanghuy
Copy link
Copy Markdown
Contributor Author

@mnriem — addressed the latest Copilot finding by updating the PR description itself (no code change).

The finding. Copilot caught that the PR description and the ## Examples block still advertised SPECIFY_INTEGRATION_<KEY>_EXTRA_ARGS, even though commit 101f35b had already renamed the actual env var to SPECKIT_INTEGRATION_<KEY>_EXTRA_ARGS everywhere in code and tests. Operators reading the PR / release notes would have set the wrong variable and silently gotten a no-op — a real footgun.

Audit for similar drift. Grepped the whole branch for any remaining SPECIFY_INTEGRATION_* / SPECIFY_*_EXTRA_ARGS references after the rename:

  • src/specify_cli/**/*.py → 0 hits, all use SPECKIT_INTEGRATION_*.
  • tests/integrations/test_extra_args.py → 0 hits, all monkeypatch.setenv calls + docstrings + autouse-fixture scope filter use SPECKIT_INTEGRATION_*.
  • CHANGELOG.md, README.md, docs/, workflows/ → 0 hits (this PR doesn't touch any of them).
  • PR description body → 10 stale hits across the intro paragraph, ### What changed, ### Default behaviour preserved, ### Examples block, the ## Testing checklist, and the manual-test-results table.

All 10 PR-description references updated to SPECKIT_INTEGRATION_<KEY>_EXTRA_ARGS. Also bumped the new-test count (14 → 17) and pytest totals (2950/35 → 3011/40) to reflect the actual current state after the intervening review iterations (malformed-quoting actionable error + MarkdownIntegration / TomlIntegration base-class coverage). Added a one-line note explaining the prefix evolution so reviewers reading historical comments aren't confused.

No code change — the implementation has been on SPECKIT_INTEGRATION_* since 101f35b.

State. Branch is MERGEABLE, all 11 CI checks green on the latest commit, no regressions. Ready for another look whenever you have a moment.

AI disclosure: drafted with Claude Opus, human-reviewed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new

@mnriem mnriem self-requested a review May 28, 2026 18:57
@mnriem mnriem merged commit f50839a into github:main May 28, 2026
11 checks passed
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 28, 2026

Thank you!

Copilot AI added a commit that referenced this pull request May 28, 2026
Adds `IntegrationBase._resolve_executable()` which reads
`SPECKIT_INTEGRATION_<KEY>_EXECUTABLE` (hyphens→underscores, uppercased)
and falls back to `self.key` when unset or whitespace-only.

All concrete `build_exec_args()` implementations now call
`self._resolve_executable()` instead of hard-coding `self.key` or
`"agy"` as the first argv token:
- MarkdownIntegration, TomlIntegration, SkillsIntegration (base classes)
- CodexIntegration, DevinIntegration, OpencodeIntegration, HermesIntegration, AgyIntegration
- CopilotIntegration (overrides `_resolve_executable()` to fall back to
  the platform-specific `_copilot_executable()` default; also updates
  `dispatch_command()` to use `_resolve_executable()`)

Tests added to tests/integrations/test_extra_args.py covering:
- default (unset) falls back to key
- env var replaces first argv token
- whitespace-only env var is a no-op
- key hyphen→underscore normalisation
- cross-integration scoping (wrong key ignored)
- all override integrations (Codex, Devin, Opencode, Copilot)
- Copilot dispatch_command path
- EXECUTABLE and EXTRA_ARGS can be set simultaneously

See issue #2596."
mnriem pushed a commit that referenced this pull request May 29, 2026
* Initial plan

* feat: support SPECKIT_INTEGRATION_<KEY>_EXECUTABLE env var override

Adds `IntegrationBase._resolve_executable()` which reads
`SPECKIT_INTEGRATION_<KEY>_EXECUTABLE` (hyphens→underscores, uppercased)
and falls back to `self.key` when unset or whitespace-only.

All concrete `build_exec_args()` implementations now call
`self._resolve_executable()` instead of hard-coding `self.key` or
`"agy"` as the first argv token:
- MarkdownIntegration, TomlIntegration, SkillsIntegration (base classes)
- CodexIntegration, DevinIntegration, OpencodeIntegration, HermesIntegration, AgyIntegration
- CopilotIntegration (overrides `_resolve_executable()` to fall back to
  the platform-specific `_copilot_executable()` default; also updates
  `dispatch_command()` to use `_resolve_executable()`)

Tests added to tests/integrations/test_extra_args.py covering:
- default (unset) falls back to key
- env var replaces first argv token
- whitespace-only env var is a no-op
- key hyphen→underscore normalisation
- cross-integration scoping (wrong key ignored)
- all override integrations (Codex, Devin, Opencode, Copilot)
- Copilot dispatch_command path
- EXECUTABLE and EXTRA_ARGS can be set simultaneously

See issue #2596."

* fix: complete docstring sentence in _resolve_executable

* test: generalize extra-args test comments for override coverage

* Fix stale Codex executable comment

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Inject extra CLI flags into the agent subprocess via env var

3 participants