Skip to content

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

Open
doquanghuy wants to merge 2 commits into
github:mainfrom
doquanghuy:feat/integrations-extra-args
Open

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

Conversation

@doquanghuy
Copy link
Copy Markdown

Description

Closes #2595.

Adds a per-integration env-var hook
(SPECIFY_<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.

What changed

  • New helper IntegrationBase._apply_extra_args_env_var(args)
    reads SPECIFY_<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.
  • New tests/integrations/test_extra_args.py with 7 tests
    covering: default no-op, single-token, multi-token via shlex,
    whitespace-only treated as unset, other-integration env var
    ignored (per-key scoping), key normalization
    (kiro-cliKIRO_CLI), requires_cli: False
    short-circuit.

Default behaviour preserved

When SPECIFY_<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
SPECIFY_CLAUDE_EXTRA_ARGS="--dangerously-skip-permissions" \
    specify workflow run my-pipeline

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

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

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests: pytest tests/ -q
    2943 passed, 35 skipped (was 2936 before; +7 new tests
    added in this PR).
  • Tested with a sample project: invoked specify workflow run
    against a Claude command step with
    SPECIFY_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 SPECIFY_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 SPECIFY_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.

…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>
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 on lines +143 to +166
def _apply_extra_args_env_var(self, args: list[str]) -> None:
"""Append `SPECIFY_<KEY>_EXTRA_ARGS` env-var value to *args*.

Operators can inject extra CLI flags into the spawned agent
subprocess by setting an env var named for the integration key,
e.g. `SPECIFY_CLAUDE_EXTRA_ARGS="--dangerously-skip-permissions"`.
Hyphens in the integration key are replaced with underscores
and the key is uppercased
(e.g. `kiro-cli` → `SPECIFY_KIRO_CLI_EXTRA_ARGS`).

Useful in CI / non-interactive contexts where the spawned agent
needs flags that change its prompt-handling behaviour.
Default behaviour (env var unset or whitespace-only) is a no-op
— *args* is unchanged. Multi-token values are parsed via
`shlex.split`.

See issue #2595.
"""
env_name = (
f"SPECIFY_{self.key.upper().replace('-', '_')}_EXTRA_ARGS"
)
extra = os.environ.get(env_name, "").strip()
if extra:
args.extend(shlex.split(extra))
Comment thread tests/integrations/test_extra_args.py Outdated
def _clean_extra_args_env(monkeypatch):
"""Strip any leaked SPECIFY_*_EXTRA_ARGS from the test env so a
developer's shell setting doesn't pollute results."""
for key in list(__import__("os").environ):
…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
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.

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