From fd561b59bc4b1bd46419848a1babc388d31d5321 Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Sat, 23 May 2026 16:54:30 -0400 Subject: [PATCH] fix(dispatcher): treat --print as non-subcommand Claude session (#1341) * fix(dispatcher): treat --print as non-subcommand Claude session * style: apply prettier formatting * fix(dispatcher): correct return type in subcommand detector getClaudeSubcommandName returns string | null; return false (boolean) was invalid after dev refactored isClaudeSubcommandInvocation to delegate to it. Change to return null to preserve the --print safety fix intent. --- src/utils/claude-subcommand-detector.ts | 10 +++++++--- tests/unit/utils/claude-subcommand-detector.test.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/utils/claude-subcommand-detector.ts b/src/utils/claude-subcommand-detector.ts index 9f425e82..4ecdaf8d 100644 --- a/src/utils/claude-subcommand-detector.ts +++ b/src/utils/claude-subcommand-detector.ts @@ -119,9 +119,11 @@ const SUBCOMMAND_ALLOWED_SESSION_FLAGS: Record> = { * * Heuristic: * 1. Walk args until the `--` terminator or end. - * 2. Skip known value-taking flags together with their next token. - * 3. Skip unknown `--flag=value` forms and bare `--flag` / `-x` tokens. - * 4. The first remaining positional token is the candidate. + * 2. If `--print` is present before the first positional token, treat as + * prompt/headless session mode (never a subcommand launch). + * 3. Skip known value-taking flags together with their next token. + * 4. Skip unknown `--flag=value` forms and bare `--flag` / `-x` tokens. + * 5. The first remaining positional token is the candidate. * 5. Match against CLAUDE_SUBCOMMANDS. * * Anything after the candidate is irrelevant — once a subcommand is in play, @@ -142,6 +144,8 @@ export function getClaudeSubcommandName(args: readonly string[]): string | null const arg = args[i]; if (arg === '--') return null; + if (arg === '--print') return null; + if (arg.startsWith('-')) { if (VALUE_TAKING_FLAGS.has(arg)) { // Skip the next token as the flag's value (when present and not another flag). diff --git a/tests/unit/utils/claude-subcommand-detector.test.ts b/tests/unit/utils/claude-subcommand-detector.test.ts index ae6f0488..27492e0c 100644 --- a/tests/unit/utils/claude-subcommand-detector.test.ts +++ b/tests/unit/utils/claude-subcommand-detector.test.ts @@ -56,6 +56,12 @@ describe('isClaudeSubcommandInvocation', () => { expect(isClaudeSubcommandInvocation(['--name', 'auth'])).toBe(false); }); + + it('treats --print prompt mode as non-subcommand even with subcommand-like prompt text', () => { + expect(isClaudeSubcommandInvocation(['--print', 'agents'])).toBe(false); + expect(isClaudeSubcommandInvocation(['--print', 'doctor'])).toBe(false); + }); + it('handles --flag=value forms', () => { expect(isClaudeSubcommandInvocation(['--model=sonnet', 'agents'])).toBe(true); }); @@ -201,6 +207,13 @@ describe('subcommand passthrough — injectors short-circuit', () => { expect(appendBrowserToolArgs(['remote-control'])).toEqual(['remote-control']); }); + + it('injectors still inject in --print prompt mode with subcommand-like prompt text', () => { + const out = appendThirdPartyWebSearchToolArgs(['--print', 'agents']); + expect(out).toContain('--append-system-prompt'); + expect(out).toContain('--disallowedTools'); + }); + it('injectors still inject for non-subcommand interactive launches', () => { const out = appendThirdPartyWebSearchToolArgs(['fix the bug']); expect(out).toContain('--append-system-prompt');