From b4455af306b93536bce969fa3d6a145386d9221c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B8=EC=9D=80?= <139741006+seeun0210@users.noreply.github.com> Date: Mon, 22 Sep 2025 06:16:21 +0900 Subject: [PATCH] fix: resolve positional prompt argument being ignored with other flags (#9004) --- .../src/commands/extensions/install.test.ts | 10 ++++-- .../cli/src/commands/extensions/new.test.ts | 4 +-- .../src/commands/extensions/uninstall.test.ts | 5 ++- packages/cli/src/config/config.test.ts | 31 +++++++++++++++++++ packages/cli/src/config/config.ts | 5 ++- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/extensions/install.test.ts b/packages/cli/src/commands/extensions/install.test.ts index c971e3213b..7b33b7fe5d 100644 --- a/packages/cli/src/commands/extensions/install.test.ts +++ b/packages/cli/src/commands/extensions/install.test.ts @@ -27,14 +27,20 @@ describe('extensions install command', () => { }); it('should fail if both git source and local path are provided', () => { - const validationParser = yargs([]).command(installCommand).fail(false); + const validationParser = yargs([]) + .command(installCommand) + .fail(false) + .locale('en'); expect(() => validationParser.parse('install some-url --path /some/path'), ).toThrow('Arguments source and path are mutually exclusive'); }); it('should fail if both auto update and local path are provided', () => { - const validationParser = yargs([]).command(installCommand).fail(false); + const validationParser = yargs([]) + .command(installCommand) + .fail(false) + .locale('en'); expect(() => validationParser.parse( 'install some-url --path /some/path --auto-update', diff --git a/packages/cli/src/commands/extensions/new.test.ts b/packages/cli/src/commands/extensions/new.test.ts index e5413310d5..c6388244af 100644 --- a/packages/cli/src/commands/extensions/new.test.ts +++ b/packages/cli/src/commands/extensions/new.test.ts @@ -27,14 +27,14 @@ describe('extensions new command', () => { }); it('should fail if no path is provided', async () => { - const parser = yargs([]).command(newCommand).fail(false); + const parser = yargs([]).command(newCommand).fail(false).locale('en'); await expect(parser.parseAsync('new')).rejects.toThrow( 'Not enough non-option arguments: got 0, need at least 2', ); }); it('should fail if no template is provided', async () => { - const parser = yargs([]).command(newCommand).fail(false); + const parser = yargs([]).command(newCommand).fail(false).locale('en'); await expect(parser.parseAsync('new /some/path')).rejects.toThrow( 'Not enough non-option arguments: got 1, need at least 2', ); diff --git a/packages/cli/src/commands/extensions/uninstall.test.ts b/packages/cli/src/commands/extensions/uninstall.test.ts index 927e805b31..e202845878 100644 --- a/packages/cli/src/commands/extensions/uninstall.test.ts +++ b/packages/cli/src/commands/extensions/uninstall.test.ts @@ -10,7 +10,10 @@ import yargs from 'yargs'; describe('extensions uninstall command', () => { it('should fail if no source is provided', () => { - const validationParser = yargs([]).command(uninstallCommand).fail(false); + const validationParser = yargs([]) + .command(uninstallCommand) + .fail(false) + .locale('en'); expect(() => validationParser.parse('uninstall')).toThrow( 'Not enough non-option arguments: got 0, need at least 1', ); diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index c101da7543..d20d0ae9f7 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -1985,6 +1985,37 @@ describe('loadCliConfig interactive', () => { const config = await loadCliConfig({}, [], 'test-session', argv); expect(config.isInteractive()).toBe(false); }); + + it('should not be interactive if positional prompt words are provided with other flags', async () => { + process.stdin.isTTY = true; + process.argv = ['node', 'script.js', '--model', 'gemini-1.5-pro', 'Hello']; + const argv = await parseArguments({} as Settings); + const config = await loadCliConfig({}, [], 'test-session', argv); + expect(config.isInteractive()).toBe(false); + }); + + it('should not be interactive if positional prompt words are provided with multiple flags', async () => { + process.stdin.isTTY = true; + process.argv = [ + 'node', + 'script.js', + '--model', + 'gemini-1.5-pro', + '--sandbox', + 'Hello world', + ]; + const argv = await parseArguments({} as Settings); + const config = await loadCliConfig({}, [], 'test-session', argv); + expect(config.isInteractive()).toBe(false); + }); + + it('should be interactive if no positional prompt words are provided with flags', async () => { + process.stdin.isTTY = true; + process.argv = ['node', 'script.js', '--model', 'gemini-1.5-pro']; + const argv = await parseArguments({} as Settings); + const config = await loadCliConfig({}, [], 'test-session', argv); + expect(config.isInteractive()).toBe(true); + }); }); describe('loadCliConfig approval mode', () => { diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index e5dd0c7510..1addcc1651 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -505,8 +505,11 @@ export async function loadCliConfig( const policyEngineConfig = createPolicyEngineConfig(settings, approvalMode); + // Fix: If promptWords are provided, always use non-interactive mode + const hasPromptWords = argv.promptWords && argv.promptWords.length > 0; const interactive = - !!argv.promptInteractive || (process.stdin.isTTY && question.length === 0); + !!argv.promptInteractive || + (process.stdin.isTTY && !hasPromptWords && !argv.prompt); // In non-interactive mode, exclude tools that require a prompt. const extraExcludes: string[] = []; if (!interactive && !argv.experimentalAcp) {